mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Move tests to reflect package structure
This commit is contained in:
242
test/unit/@node-red/runtime/lib/api/comms_spec.js
Normal file
242
test/unit/@node-red/runtime/lib/api/comms_spec.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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 comms = require("../../../red/runtime-api/comms");
|
||||
|
||||
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) {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
comms.addConnection({client: clientConnection}).then(done);
|
||||
})
|
||||
after(function(done) {
|
||||
comms.removeConnection({client: clientConnection}).then(done);
|
||||
})
|
||||
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: "my-status"
|
||||
})
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","status/my-event");
|
||||
messages[0].should.have.property("data","my-status")
|
||||
})
|
||||
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() {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
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() {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
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('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);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
19
test/unit/@node-red/runtime/lib/api/context_spec.js
Normal file
19
test/unit/@node-red/runtime/lib/api/context_spec.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
describe("runtime-api/context", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
});
|
413
test/unit/@node-red/runtime/lib/api/flows_spec.js
Normal file
413
test/unit/@node-red/runtime/lib/api/flows_spec.js
Normal file
@@ -0,0 +1,413 @@
|
||||
/**
|
||||
* 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 flows = require("../../../red/runtime-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(),
|
||||
nodes: {
|
||||
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,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(),
|
||||
nodes: {
|
||||
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[1].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[1].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[1].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(),
|
||||
nodes: {
|
||||
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(),
|
||||
nodes: {
|
||||
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;
|
||||
throw err;
|
||||
} 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(),
|
||||
nodes: {
|
||||
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;
|
||||
throw err;
|
||||
} 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(),
|
||||
nodes: {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
54
test/unit/@node-red/runtime/lib/api/index_spec.js
Normal file
54
test/unit/@node-red/runtime/lib/api/index_spec.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 index = require("../../../red/runtime-api/index");
|
||||
|
||||
|
||||
describe("runtime-api/index", function() {
|
||||
before(function() {
|
||||
["comms","flows","nodes","settings","library","projects"].forEach(n => {
|
||||
sinon.stub(require(`../../../red/runtime-api/${n}`),"init",()=>{});
|
||||
})
|
||||
});
|
||||
after(function() {
|
||||
["comms","flows","nodes","settings","library","projects"].forEach(n => {
|
||||
require(`../../../red/runtime-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);
|
||||
})
|
||||
|
||||
});
|
537
test/unit/@node-red/runtime/lib/api/library_spec.js
Normal file
537
test/unit/@node-red/runtime/lib/api/library_spec.js
Normal file
@@ -0,0 +1,537 @@
|
||||
/**
|
||||
* 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 library = require("../../../red/runtime-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(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({type: "known", path: "/abc"}).then(function(result) {
|
||||
result.should.eql("known")
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects a forbidden entry", function(done) {
|
||||
library.getEntry({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({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({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({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(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({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({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({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)
|
||||
})
|
||||
})
|
||||
describe("getEntries", function() {
|
||||
var opts;
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
storage: {
|
||||
getAllFlows: function() {
|
||||
return Promise.resolve({a:1});
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlows: function() {
|
||||
return {b:2};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
it("returns all flows", function(done) {
|
||||
library.getEntries({type:"flows"}).then(function(result) {
|
||||
result.should.eql({a:1,d:{_examples_:{b:2}}});
|
||||
done();
|
||||
}).catch(done)
|
||||
});
|
||||
it("fails for non-flows (currently)", function(done) {
|
||||
library.getEntries({type:"functions"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var fs = require("fs");
|
||||
var fspath = require('path');
|
||||
var request = require('supertest');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
var when = require('when');
|
||||
|
||||
var app;
|
||||
var library = require("../../../../red/api/editor/library");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
|
||||
describe("api/editor/library", function() {
|
||||
|
||||
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
|
||||
var flows = _flows;
|
||||
var libraryEntries = _libraryEntries;
|
||||
library.init(app,{
|
||||
log:{audit:function(){},_:function(){},warn:function(){}},
|
||||
storage: {
|
||||
init: function() {
|
||||
return when.resolve();
|
||||
},
|
||||
getAllFlows: function() {
|
||||
return when.resolve(flows);
|
||||
},
|
||||
getFlow: function(fn) {
|
||||
if (flows[fn]) {
|
||||
return when.resolve(flows[fn]);
|
||||
} else if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveFlow: function(fn,data) {
|
||||
if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
flows[fn] = data;
|
||||
return when.resolve();
|
||||
},
|
||||
getLibraryEntry: function(type,path) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
if (libraryEntries[type] && libraryEntries[type][path]) {
|
||||
return when.resolve(libraryEntries[type][path]);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveLibraryEntry: function(type,path,meta,body) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
libraryEntries[type][path] = body;
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
events: {
|
||||
on: function(){},
|
||||
removeListener: function(){}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlows: function() {
|
||||
return _examples;
|
||||
},
|
||||
getNodeExampleFlowPath: _exampleFlowPathFunction
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("flows", function() {
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/library/flows",library.getAll);
|
||||
app.post(new RegExp("/library/flows\/(.*)"),library.post);
|
||||
app.get(new RegExp("/library/flows\/(.*)"),library.get);
|
||||
app.response.sendFile = function (path) {
|
||||
app.response.json.call(this, {sendFile: path});
|
||||
};
|
||||
sinon.stub(fs,"statSync",function() { return true; });
|
||||
});
|
||||
after(function() {
|
||||
fs.statSync.restore();
|
||||
});
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
res.body.should.not.have.property('d');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
var flow = '[]';
|
||||
request(app)
|
||||
.post('/library/flows/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({f:["bar"]});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('f');
|
||||
should.deepEqual(res.body.f,['bar']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 403 for malicious get attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.get('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('returns 403 for malicious post attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.post('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('includes examples flows if set', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples);
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('d');
|
||||
res.body.d.should.have.property('_examples_');
|
||||
should.deepEqual(res.body.d._examples_,examples);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve an example flow', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/node-module/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'node-module:example-one');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve an example flow in an org scoped package', function(done) {
|
||||
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/@org_scope/node_package/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'@org_scope/node_package:example-one');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("type", function() {
|
||||
before(function() {
|
||||
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
initLibrary({},{});
|
||||
auth.init({settings:{}});
|
||||
library.register("test");
|
||||
});
|
||||
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{'test':{"":[]}});
|
||||
request(app)
|
||||
.get('/library/test')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{});
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{'test':{}});
|
||||
var flow = {text:"test content"};
|
||||
request(app)
|
||||
.post('/library/test/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow.text);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({},{'test':{'a':['abc','def']}});
|
||||
request(app)
|
||||
.get('/library/test/a')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// This response isn't strictly accurate - but it
|
||||
// verifies the api returns what storage gave it
|
||||
should.deepEqual(res.body,['abc','def']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.post('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.send('root:x:0:0:root:/root:/usr/bin/tclsh')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
1005
test/unit/@node-red/runtime/lib/api/nodes_spec.js
Normal file
1005
test/unit/@node-red/runtime/lib/api/nodes_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
1109
test/unit/@node-red/runtime/lib/api/projects_spec.js
Normal file
1109
test/unit/@node-red/runtime/lib/api/projects_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
670
test/unit/@node-red/runtime/lib/api/settings_spec.js
Normal file
670
test/unit/@node-red/runtime/lib/api/settings_spec.js
Normal file
@@ -0,0 +1,670 @@
|
||||
/**
|
||||
* 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 settings = require("../../../red/runtime-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.skip("getRuntimeSettings", function() {});
|
||||
describe.skip("getUserSettings", function() {});
|
||||
describe.skip("updateUserSettings", function() {});
|
||||
describe.skip("getUserKeys", function() {});
|
||||
describe.skip("getUserKey", function() {});
|
||||
describe.skip("generateUserKey", function() {});
|
||||
describe.skip("removeUserKey", function() {});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
before(function() {
|
||||
sinon.stub(theme,"settings",function() { return { test: 456 };});
|
||||
app = express();
|
||||
app.get("/settings",info.runtimeSettings);
|
||||
app.get("/settingsWithUser",function(req,res,next) {
|
||||
req.user = {
|
||||
username: "nick",
|
||||
permissions: "*",
|
||||
image: "http://example.com",
|
||||
anonymous: false,
|
||||
private: "secret"
|
||||
}
|
||||
next();
|
||||
},info.runtimeSettings);
|
||||
});
|
||||
after(function() {
|
||||
theme.settings.restore();
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('returns the filtered settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme",{test:456});
|
||||
res.body.should.have.property("testNodeSetting","helloWorld");
|
||||
res.body.should.not.have.property("foo",123);
|
||||
res.body.should.have.property("flowEncryptionType","test-key-type");
|
||||
res.body.should.not.have.property("user");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns the filtered user in settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settingsWithUser")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("user");
|
||||
res.body.user.should.have.property("username","nick");
|
||||
res.body.user.should.have.property("permissions","*");
|
||||
res.body.user.should.have.property("image","http://example.com");
|
||||
res.body.user.should.have.property("anonymous",false);
|
||||
res.body.user.should.not.have.property("private");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('includes project settings if projects available', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
getActiveProject: () => 'test-active-project',
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("project","test-active-project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('includes existing files details if projects enabled but no active project and files exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => true,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.have.property("files");
|
||||
res.body.files.should.have.property("flow",'test-flow-file');
|
||||
res.body.files.should.have.property("credentials",'test-creds-file');
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('does not include file details if projects enabled but no active project and files do not exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => false,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('overrides palette editable if runtime says it is disabled', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function() {}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return false; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme");
|
||||
res.body.editorTheme.should.have.property("test",456);
|
||||
|
||||
res.body.editorTheme.should.have.property("palette",{editable:false});
|
||||
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 when = require("when");
|
||||
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 when.resolve(session_data);
|
||||
},
|
||||
setSessions: function(_session) {
|
||||
session_data = _session;
|
||||
return when.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: {paletteEditorEnabled: 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
23
test/unit/@node-red/runtime/lib/events_spec.js
Normal file
23
test/unit/@node-red/runtime/lib/events_spec.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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");
|
||||
|
||||
describe("runtime/events", function() {
|
||||
it('can be required without errors', function() {
|
||||
require("../../../red/runtime/events");
|
||||
});
|
||||
it.skip('more tests needed', function(){})
|
||||
});
|
236
test/unit/@node-red/runtime/lib/index_spec.js
Normal file
236
test/unit/@node-red/runtime/lib/index_spec.js
Normal file
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* 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 api = require("../../../red/api");
|
||||
var runtime = require("../../../red/runtime");
|
||||
|
||||
var redNodes = require("../../../red/runtime/nodes");
|
||||
var storage = require("../../../red/runtime/storage");
|
||||
var settings = require("../../../red/runtime/settings");
|
||||
var log = require("../../../red/util/log");
|
||||
|
||||
describe("runtime", function() {
|
||||
afterEach(function() {
|
||||
if (console.log.restore) {
|
||||
console.log.restore();
|
||||
}
|
||||
})
|
||||
|
||||
before(function() {
|
||||
process.env.NODE_RED_HOME = path.resolve(path.join(__dirname,"..","..",".."))
|
||||
});
|
||||
after(function() {
|
||||
delete process.env.NODE_RED_HOME;
|
||||
});
|
||||
function mockUtil(metrics) {
|
||||
return {
|
||||
log:{
|
||||
log: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
info: sinon.stub(),
|
||||
trace: sinon.stub(),
|
||||
metric: sinon.stub().returns(!!metrics),
|
||||
_: function() { return "abc"}
|
||||
},
|
||||
i18n: {
|
||||
registerMessageCatalog: function(){
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
describe("init", function() {
|
||||
beforeEach(function() {
|
||||
sinon.stub(log,"init",function() {});
|
||||
sinon.stub(settings,"init",function() {});
|
||||
sinon.stub(redNodes,"init",function() {})
|
||||
});
|
||||
afterEach(function() {
|
||||
log.init.restore();
|
||||
settings.init.restore();
|
||||
redNodes.init.restore();
|
||||
})
|
||||
|
||||
it("initialises components", function() {
|
||||
runtime.init({testSettings: true, httpAdminRoot:"/"},mockUtil());
|
||||
settings.init.called.should.be.true();
|
||||
redNodes.init.called.should.be.true();
|
||||
});
|
||||
|
||||
it("returns version", function() {
|
||||
runtime.init({testSettings: true, httpAdminRoot:"/"},mockUtil());
|
||||
/^\d+\.\d+\.\d+(-git)?$/.test(runtime.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",function(settings) {return Promise.resolve();});
|
||||
redNodesInit = sinon.stub(redNodes,"init", function() {});
|
||||
redNodesLoad = sinon.stub(redNodes,"load", function() {return Promise.resolve()});
|
||||
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
|
||||
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return Promise.resolve()});
|
||||
redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {});
|
||||
redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return Promise.resolve()});
|
||||
});
|
||||
afterEach(function() {
|
||||
storageInit.restore();
|
||||
redNodesInit.restore();
|
||||
redNodesLoad.restore();
|
||||
redNodesGetNodeList.restore();
|
||||
redNodesCleanModuleList.restore();
|
||||
redNodesLoadFlows.restore();
|
||||
redNodesStartFlows.restore();
|
||||
redNodesLoadContextsPlugin.restore();
|
||||
});
|
||||
it("reports errored/missing modules",function(done) {
|
||||
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) {
|
||||
return [
|
||||
{ err:"errored",name:"errName" }, // error
|
||||
{ module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing
|
||||
].filter(cb);
|
||||
});
|
||||
var util = mockUtil();
|
||||
runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
|
||||
// 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();
|
||||
|
||||
util.log.warn.calledWithMatch("Failed to register 1 node type");
|
||||
util.log.warn.calledWithMatch("Missing node modules");
|
||||
util.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", 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",function(name) { return Promise.resolve({nodes:[]});});
|
||||
var util = mockUtil();
|
||||
runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
|
||||
sinon.stub(console,"log");
|
||||
runtime.start().then(function() {
|
||||
console.log.restore();
|
||||
try {
|
||||
util.log.warn.calledWithMatch("Failed to register 2 node types");
|
||||
util.log.warn.calledWithMatch("Missing node modules");
|
||||
util.log.warn.calledWithMatch(" - module: typeA, typeB");
|
||||
util.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", function(cb) {
|
||||
return [
|
||||
{ err:"errored",name:"errName" } // error
|
||||
].filter(cb);
|
||||
});
|
||||
var util = mockUtil();
|
||||
runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
|
||||
sinon.stub(console,"log");
|
||||
runtime.start().then(function() {
|
||||
console.log.restore();
|
||||
try {
|
||||
util.log.warn.neverCalledWithMatch("Failed to register 1 node type");
|
||||
util.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",function() { return Promise.resolve();} );
|
||||
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
|
||||
var util = mockUtil(true);
|
||||
runtime.init({testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
|
||||
sinon.stub(console,"log");
|
||||
runtime.start().then(function() {
|
||||
console.log.restore();
|
||||
setTimeout(function() {
|
||||
try {
|
||||
util.log.log.args.should.have.lengthOf(3);
|
||||
util.log.log.args[0][0].should.have.property("event","runtime.memory.rss");
|
||||
util.log.log.args[1][0].should.have.property("event","runtime.memory.heapTotal");
|
||||
util.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",function() { return Promise.resolve();} );
|
||||
var closeContextsPlugin = sinon.stub(redNodes,"closeContextsPlugin",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)
|
||||
});
|
||||
});
|
||||
});
|
180
test/unit/@node-red/runtime/lib/library/index_spec.js
Normal file
180
test/unit/@node-red/runtime/lib/library/index_spec.js
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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 library = require("../../../../red/runtime/library/index")
|
||||
|
||||
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() {
|
||||
describe("register", function() {
|
||||
// it("throws error for duplicate type", function() {
|
||||
// library.init({});
|
||||
// library.register("unknown","/abc");
|
||||
// should(()=>{library.register("unknown","/abc")} ).throw();
|
||||
// })
|
||||
})
|
||||
describe("getEntry", function() {
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
storage: {
|
||||
getLibraryEntry: function(type,path) {
|
||||
return Promise.resolve({type,path});
|
||||
},
|
||||
getFlow: function(path) {
|
||||
return Promise.resolve({path});
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlowPath: function(module,entryPath) {
|
||||
if (module === "unknown") {
|
||||
return null;
|
||||
}
|
||||
return "/tmp/"+module+"/"+entryPath;
|
||||
}
|
||||
}
|
||||
});
|
||||
sinon.stub(fs,"readFile", 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('throws error for unregistered type', function() {
|
||||
should(()=>{library.getEntry("unknown","/abc")} ).throw();
|
||||
});
|
||||
|
||||
it('returns a registered non-flow entry', function(done) {
|
||||
library.register("test-module","test-type");
|
||||
library.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) {
|
||||
library.getEntry("flows","/abc").then(function(result) {
|
||||
result.should.have.property("path","/abc")
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it ('returns a flow example entry', function(done) {
|
||||
library.getEntry("flows","_examples_/test-module/abc").then(function(result) {
|
||||
result.should.eql("Example flow result");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it ('returns a flow example entry from scoped module', function(done) {
|
||||
library.getEntry("flows","_examples_/@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) {
|
||||
library.getEntry("flows","_examples_/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) {
|
||||
library.getEntry("flows","_examples_/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) {
|
||||
library.getEntry("flows","_examples_/test-module/throw").then(function(result) {
|
||||
done(new Error("No error thrown"))
|
||||
}).catch(function(err) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveEntry", function() {
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
storage: {
|
||||
saveLibraryEntry: function(type, path, meta, body) {
|
||||
return Promise.resolve({type,path,meta,body})
|
||||
},
|
||||
saveFlow: function(path,body) {
|
||||
return Promise.resolve({path,body});
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlowPath: function(module,entryPath) {
|
||||
if (module === "unknown") {
|
||||
return null;
|
||||
}
|
||||
return "/tmp/"+module+"/"+entryPath;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throws error for unregistered type', function() {
|
||||
should(()=>{library.saveEntry("unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
|
||||
});
|
||||
it('saves a flow entry', function(done) {
|
||||
library.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) {
|
||||
library.register("test-module","test-type");
|
||||
library.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);
|
||||
})
|
||||
|
||||
});
|
||||
});
|
547
test/unit/@node-red/runtime/lib/nodes/Node_spec.js
Normal file
547
test/unit/@node-red/runtime/lib/nodes/Node_spec.js
Normal file
@@ -0,0 +1,547 @@
|
||||
/**
|
||||
* 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 RedNode = require("../../../../red/runtime/nodes/Node");
|
||||
var Log = require("../../../../red/util/log");
|
||||
var flows = require("../../../../red/runtime/nodes/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();
|
||||
});
|
||||
var p = n.close();
|
||||
should.not.exist(p);
|
||||
});
|
||||
|
||||
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",function(err,msg) {});
|
||||
var message = {payload:"hello world"};
|
||||
n.on('input',function(msg) {
|
||||
throw new Error("test error");
|
||||
});
|
||||
n.receive(message);
|
||||
n.error.called.should.be.true();
|
||||
n.error.firstCall.args[1].should.equal(message);
|
||||
done();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('#send', function() {
|
||||
|
||||
it('emits a single message', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2}[id];
|
||||
});
|
||||
var message = {payload:"hello world"};
|
||||
|
||||
n2.on('input',function(msg) {
|
||||
// msg equals message, and is not a new copy
|
||||
should.deepEqual(msg,message);
|
||||
should.strictEqual(msg,message);
|
||||
flowGet.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
n1.send(message);
|
||||
});
|
||||
|
||||
it('emits multiple messages on a single output', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2}[id];
|
||||
});
|
||||
|
||||
var messages = [
|
||||
{payload:"hello world"},
|
||||
{payload:"hello world again"}
|
||||
];
|
||||
|
||||
var rcvdCount = 0;
|
||||
|
||||
n2.on('input',function(msg) {
|
||||
if (rcvdCount === 0) {
|
||||
// first msg sent, don't clone
|
||||
should.deepEqual(msg,messages[rcvdCount]);
|
||||
should.strictEqual(msg,messages[rcvdCount]);
|
||||
rcvdCount += 1;
|
||||
} else {
|
||||
// second msg sent, clone
|
||||
msg.payload.should.equal(messages[rcvdCount].payload);
|
||||
should.notStrictEqual(msg,messages[rcvdCount]);
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
n1.send([messages]);
|
||||
});
|
||||
|
||||
it('emits messages to multiple outputs', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
||||
var n4 = new RedNode({id:'n4',type:'abc'});
|
||||
var n5 = new RedNode({id:'n5',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id];
|
||||
});
|
||||
|
||||
var messages = [
|
||||
{payload:"hello world"},
|
||||
null,
|
||||
{payload:"hello world again"}
|
||||
];
|
||||
|
||||
var rcvdCount = 0;
|
||||
|
||||
// first message sent, don't clone
|
||||
// message uuids should match
|
||||
n2.on('input',function(msg) {
|
||||
should.deepEqual(msg,messages[0]);
|
||||
should.strictEqual(msg,messages[0]);
|
||||
rcvdCount += 1;
|
||||
if (rcvdCount == 3) {
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
n3.on('input',function(msg) {
|
||||
should.fail(null,null,"unexpected message");
|
||||
});
|
||||
|
||||
// second message sent, clone
|
||||
// message uuids wont match since we've cloned
|
||||
n4.on('input',function(msg) {
|
||||
msg.payload.should.equal(messages[2].payload);
|
||||
should.notStrictEqual(msg,messages[2]);
|
||||
rcvdCount += 1;
|
||||
if (rcvdCount == 3) {
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
// third message sent, clone
|
||||
// message uuids wont match since we've cloned
|
||||
n5.on('input',function(msg) {
|
||||
msg.payload.should.equal(messages[2].payload);
|
||||
should.notStrictEqual(msg,messages[2]);
|
||||
rcvdCount += 1;
|
||||
if (rcvdCount == 3) {
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
n1.send(messages);
|
||||
});
|
||||
|
||||
it('emits no messages', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2}[id];
|
||||
});
|
||||
|
||||
n2.on('input',function(msg) {
|
||||
should.fail(null,null,"unexpected message");
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
flowGet.restore();
|
||||
done();
|
||||
}, 200);
|
||||
|
||||
n1.send();
|
||||
});
|
||||
|
||||
it('emits messages ignoring non-existent nodes', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2}[id];
|
||||
});
|
||||
|
||||
var messages = [
|
||||
{payload:"hello world"},
|
||||
{payload:"hello world again"}
|
||||
];
|
||||
|
||||
// only one message sent, so no copy needed
|
||||
n2.on('input',function(msg) {
|
||||
should.deepEqual(msg,messages[1]);
|
||||
should.strictEqual(msg,messages[1]);
|
||||
flowGet.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
n1.send(messages);
|
||||
});
|
||||
|
||||
it('emits messages without cloning req or res', function(done) {
|
||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[[['n2'],['n3']]]});
|
||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':n1,'n2':n2,'n3':n3}[id];
|
||||
});
|
||||
|
||||
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) {
|
||||
flowGet.restore();
|
||||
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) {
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
n1.send(message);
|
||||
});
|
||||
|
||||
it("logs the uuid for all messages sent", function(done) {
|
||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||
return {'n1':sender,'n2':receiver1,'n3':receiver2}[id];
|
||||
});
|
||||
var logHandler = {
|
||||
messagesSent: 0,
|
||||
emit: function(event, msg) {
|
||||
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
|
||||
this.messagesSent++;
|
||||
(typeof msg.msgid).should.not.be.equal("undefined");
|
||||
flowGet.restore();
|
||||
done();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Log.addHandler(logHandler);
|
||||
|
||||
var sender = new RedNode({id:'n1',type:'abc', wires:[['n2', 'n3']]});
|
||||
var receiver1 = new RedNode({id:'n2',type:'abc'});
|
||||
var receiver2 = new RedNode({id:'n3',type:'abc'});
|
||||
sender.send({"some": "message"});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
describe('#log', function() {
|
||||
it('produces a log message', function(done) {
|
||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
||||
var loginfo = {};
|
||||
sinon.stub(Log, 'log', function(msg) {
|
||||
loginfo = msg;
|
||||
});
|
||||
n.log("a log message");
|
||||
should.deepEqual({level:Log.INFO, id:n.id,
|
||||
type:n.type, msg:"a log message",z:'789'}, loginfo);
|
||||
Log.log.restore();
|
||||
done();
|
||||
});
|
||||
it('produces a log message with a name', function(done) {
|
||||
var n = new RedNode({id:'123', type:'abc', name:"barney", z:'789'});
|
||||
var loginfo = {};
|
||||
sinon.stub(Log, 'log', function(msg) {
|
||||
loginfo = msg;
|
||||
});
|
||||
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);
|
||||
Log.log.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#warn', function() {
|
||||
it('produces a warning message', function(done) {
|
||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
||||
var loginfo = {};
|
||||
sinon.stub(Log, 'log', function(msg) {
|
||||
loginfo = msg;
|
||||
});
|
||||
n.warn("a warning");
|
||||
should.deepEqual({level:Log.WARN, id:n.id,
|
||||
type:n.type, msg:"a warning",z:'789'}, loginfo);
|
||||
Log.log.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#error', function() {
|
||||
it('handles a null error message', function(done) {
|
||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
||||
var loginfo = {};
|
||||
sinon.stub(Log, 'log', function(msg) {
|
||||
loginfo = msg;
|
||||
});
|
||||
sinon.stub(flows,"handleError", function(node,message,msg) {
|
||||
});
|
||||
|
||||
var message = {a:1};
|
||||
|
||||
n.error(null,message);
|
||||
should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"",z:'789'}, loginfo);
|
||||
|
||||
flows.handleError.called.should.be.true();
|
||||
flows.handleError.args[0][0].should.eql(n);
|
||||
flows.handleError.args[0][1].should.eql("");
|
||||
flows.handleError.args[0][2].should.eql(message);
|
||||
|
||||
Log.log.restore();
|
||||
flows.handleError.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
it('produces an error message', function(done) {
|
||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
||||
var loginfo = {};
|
||||
sinon.stub(Log, 'log', function(msg) {
|
||||
loginfo = msg;
|
||||
});
|
||||
sinon.stub(flows,"handleError", function(node,message,msg) {
|
||||
});
|
||||
|
||||
var message = {a:2};
|
||||
|
||||
n.error("This is an error",message);
|
||||
should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"This is an error",z:'789'}, loginfo);
|
||||
|
||||
flows.handleError.called.should.be.true();
|
||||
flows.handleError.args[0][0].should.eql(n);
|
||||
flows.handleError.args[0][1].should.eql("This is an error");
|
||||
flows.handleError.args[0][2].should.eql(message);
|
||||
|
||||
Log.log.restore();
|
||||
flows.handleError.restore();
|
||||
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', 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', 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', 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) {
|
||||
sinon.stub(flows,"handleStatus", function(node,message,msg) {});
|
||||
var n = new RedNode({id:'123',type:'abc'});
|
||||
var status = {fill:"green",shape:"dot",text:"connected"};
|
||||
var topic;
|
||||
var message;
|
||||
var retain;
|
||||
|
||||
n.status(status);
|
||||
|
||||
flows.handleStatus.called.should.be.true();
|
||||
flows.handleStatus.args[0][0].should.eql(n);
|
||||
flows.handleStatus.args[0][1].should.eql(status);
|
||||
flows.handleStatus.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
900
test/unit/@node-red/runtime/lib/nodes/context/index_spec.js
Normal file
900
test/unit/@node-red/runtime/lib/nodes/context/index_spec.js
Normal file
@@ -0,0 +1,900 @@
|
||||
/**
|
||||
* 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 Context = require("../../../../../red/runtime/nodes/context/index");
|
||||
|
||||
describe('context', function() {
|
||||
describe('local memory',function() {
|
||||
beforeEach(function() {
|
||||
Context.init({});
|
||||
Context.load();
|
||||
});
|
||||
afterEach(function() {
|
||||
Context.clean({allNodes:{}});
|
||||
return Context.close();
|
||||
});
|
||||
it('stores local property',function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
should.not.exist(context1.get("foo"));
|
||||
context1.set("foo","test");
|
||||
context1.get("foo").should.equal("test");
|
||||
});
|
||||
it('stores local property - creates parent properties',function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
context1.set("foo.bar","test");
|
||||
context1.get("foo").should.eql({bar:"test"});
|
||||
});
|
||||
it('deletes local property',function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
context1.set("foo.abc.bar1","test1");
|
||||
context1.set("foo.abc.bar2","test2");
|
||||
context1.get("foo.abc").should.eql({bar1:"test1",bar2:"test2"});
|
||||
context1.set("foo.abc.bar1",undefined);
|
||||
context1.get("foo.abc").should.eql({bar2:"test2"});
|
||||
context1.set("foo.abc",undefined);
|
||||
should.not.exist(context1.get("foo.abc"));
|
||||
context1.set("foo",undefined);
|
||||
should.not.exist(context1.get("foo"));
|
||||
});
|
||||
it('stores flow property',function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
should.not.exist(context1.flow.get("foo"));
|
||||
context1.flow.set("foo","test");
|
||||
context1.flow.get("foo").should.equal("test");
|
||||
});
|
||||
it('stores global property',function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
should.not.exist(context1.global.get("foo"));
|
||||
context1.global.set("foo","test");
|
||||
context1.global.get("foo").should.equal("test");
|
||||
});
|
||||
|
||||
it('keeps local context local', function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
var context2 = Context.get("2","flowA");
|
||||
|
||||
should.not.exist(context1.get("foo"));
|
||||
should.not.exist(context2.get("foo"));
|
||||
context1.set("foo","test");
|
||||
|
||||
context1.get("foo").should.equal("test");
|
||||
should.not.exist(context2.get("foo"));
|
||||
});
|
||||
it('flow context accessible to all flow nodes', function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
var context2 = Context.get("2","flowA");
|
||||
|
||||
should.not.exist(context1.flow.get("foo"));
|
||||
should.not.exist(context2.flow.get("foo"));
|
||||
|
||||
context1.flow.set("foo","test");
|
||||
context1.flow.get("foo").should.equal("test");
|
||||
context2.flow.get("foo").should.equal("test");
|
||||
});
|
||||
|
||||
it('flow context not shared to nodes on other flows', function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
var context2 = Context.get("2","flowB");
|
||||
|
||||
should.not.exist(context1.flow.get("foo"));
|
||||
should.not.exist(context2.flow.get("foo"));
|
||||
|
||||
context1.flow.set("foo","test");
|
||||
context1.flow.get("foo").should.equal("test");
|
||||
should.not.exist(context2.flow.get("foo"));
|
||||
});
|
||||
|
||||
it('global context shared to all nodes', function() {
|
||||
var context1 = Context.get("1","flowA");
|
||||
var context2 = Context.get("2","flowB");
|
||||
|
||||
should.not.exist(context1.global.get("foo"));
|
||||
should.not.exist(context2.global.get("foo"));
|
||||
|
||||
context1.global.set("foo","test");
|
||||
context1.global.get("foo").should.equal("test");
|
||||
context2.global.get("foo").should.equal("test");
|
||||
});
|
||||
|
||||
it('deletes context',function() {
|
||||
var context = Context.get("1","flowA");
|
||||
should.not.exist(context.get("foo"));
|
||||
context.set("foo","abc");
|
||||
context.get("foo").should.equal("abc");
|
||||
|
||||
return Context.delete("1","flowA").then(function(){
|
||||
context = Context.get("1","flowA");
|
||||
should.not.exist(context.get("foo"));
|
||||
});
|
||||
});
|
||||
|
||||
it('enumerates context keys - sync', function() {
|
||||
var context = Context.get("1","flowA");
|
||||
|
||||
var keys = context.keys();
|
||||
keys.should.be.an.Array();
|
||||
keys.should.be.empty();
|
||||
|
||||
context.set("foo","bar");
|
||||
keys = context.keys();
|
||||
keys.should.have.length(1);
|
||||
keys[0].should.equal("foo");
|
||||
|
||||
context.set("abc.def","bar");
|
||||
keys = context.keys();
|
||||
keys.should.have.length(2);
|
||||
keys[1].should.equal("abc");
|
||||
});
|
||||
|
||||
it('enumerates context keys - async', function(done) {
|
||||
var context = Context.get("1","flowA");
|
||||
|
||||
var keys = context.keys(function(err,keys) {
|
||||
keys.should.be.an.Array();
|
||||
keys.should.be.empty();
|
||||
context.set("foo","bar");
|
||||
keys = context.keys(function(err,keys) {
|
||||
keys.should.have.length(1);
|
||||
keys[0].should.equal("foo");
|
||||
|
||||
context.set("abc.def","bar");
|
||||
keys = context.keys(function(err,keys) {
|
||||
keys.should.have.length(2);
|
||||
keys[1].should.equal("abc");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should enumerate only context keys when GlobalContext was given - sync', function() {
|
||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flowA");
|
||||
context.global.set("foo2","bar2");
|
||||
var keys = context.global.keys();
|
||||
keys.should.have.length(2);
|
||||
keys[0].should.equal("foo");
|
||||
keys[1].should.equal("foo2");
|
||||
});
|
||||
});
|
||||
|
||||
it('should enumerate only context keys when GlobalContext was given - async', function(done) {
|
||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flowA");
|
||||
context.global.set("foo2","bar2");
|
||||
context.global.keys(function(err,keys) {
|
||||
keys.should.have.length(2);
|
||||
keys[0].should.equal("foo");
|
||||
keys[1].should.equal("foo2");
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('returns functionGlobalContext value if store value undefined', function() {
|
||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flowA");
|
||||
var v = context.global.get('foo');
|
||||
v.should.equal('bar');
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('external context storage',function() {
|
||||
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context"));
|
||||
var sandbox = sinon.sandbox.create();
|
||||
var stubGet = sandbox.stub();
|
||||
var stubSet = sandbox.stub();
|
||||
var stubKeys = sandbox.stub();
|
||||
var stubDelete = sandbox.stub().returns(Promise.resolve());
|
||||
var stubClean = sandbox.stub().returns(Promise.resolve());
|
||||
var stubOpen = sandbox.stub().returns(Promise.resolve());
|
||||
var stubClose = sandbox.stub().returns(Promise.resolve());
|
||||
var stubGet2 = sandbox.stub();
|
||||
var stubSet2 = sandbox.stub();
|
||||
var stubKeys2 = sandbox.stub();
|
||||
var stubDelete2 = sandbox.stub().returns(Promise.resolve());
|
||||
var stubClean2 = sandbox.stub().returns(Promise.resolve());
|
||||
var stubOpen2 = sandbox.stub().returns(Promise.resolve());
|
||||
var stubClose2 = sandbox.stub().returns(Promise.resolve());
|
||||
var testPlugin = function(config){
|
||||
function Test(){}
|
||||
Test.prototype.get = stubGet;
|
||||
Test.prototype.set = stubSet;
|
||||
Test.prototype.keys = stubKeys;
|
||||
Test.prototype.delete = stubDelete;
|
||||
Test.prototype.clean = stubClean;
|
||||
Test.prototype.open = stubOpen;
|
||||
Test.prototype.close = stubClose;
|
||||
return new Test(config);
|
||||
};
|
||||
var testPlugin2 = function(config){
|
||||
function Test2(){}
|
||||
Test2.prototype.get = stubGet2;
|
||||
Test2.prototype.set = stubSet2;
|
||||
Test2.prototype.keys = stubKeys2;
|
||||
Test2.prototype.delete = stubDelete2;
|
||||
Test2.prototype.clean = stubClean2;
|
||||
Test2.prototype.open = stubOpen2;
|
||||
Test2.prototype.close = stubClose2;
|
||||
return new Test2(config);
|
||||
};
|
||||
var contextStorage={
|
||||
test:{
|
||||
module: testPlugin,
|
||||
config:{}
|
||||
}
|
||||
};
|
||||
var contextDefaultStorage={
|
||||
default: {
|
||||
module: testPlugin2,
|
||||
config:{}
|
||||
},
|
||||
test:{
|
||||
module: testPlugin,
|
||||
config:{}
|
||||
}
|
||||
};
|
||||
var contextAlias={
|
||||
default: "test",
|
||||
test:{
|
||||
module: testPlugin,
|
||||
config:{}
|
||||
}
|
||||
};
|
||||
var memoryStorage ={
|
||||
memory:{
|
||||
module: "memory"
|
||||
}
|
||||
};
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.reset();
|
||||
return Context.clean({allNodes:{}}).then(function(){
|
||||
return Context.close();
|
||||
}).then(function(){
|
||||
return fs.remove(resourcesDir);
|
||||
});
|
||||
});
|
||||
|
||||
describe('load modules',function(){
|
||||
it('should call open()', function() {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
stubOpen.called.should.be.true();
|
||||
stubOpen2.called.should.be.true();
|
||||
});
|
||||
});
|
||||
it('should load memory module', function() {
|
||||
Context.init({contextStorage:{memory:{module:"memory"}}});
|
||||
return Context.load();
|
||||
});
|
||||
it('should load localfilesystem module', function() {
|
||||
Context.init({contextStorage:{file:{module:"localfilesystem",config:{dir:resourcesDir}}}});
|
||||
return Context.load();
|
||||
});
|
||||
it('should ignore reserved storage name `_`', function(done) {
|
||||
Context.init({contextStorage:{_:{module:testPlugin}}});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){}
|
||||
context.set("foo","bar","_",cb);
|
||||
context.get("foo","_",cb);
|
||||
context.keys("_",cb);
|
||||
stubSet.called.should.be.false();
|
||||
stubGet.called.should.be.false();
|
||||
stubKeys.called.should.be.false();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should fail when using invalid store name', function(done) {
|
||||
Context.init({contextStorage:{'Invalid name':{module:testPlugin}}});
|
||||
Context.load().then(function(){
|
||||
done("An error was not thrown");
|
||||
}).catch(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail when using invalid sign character', function (done) {
|
||||
Context.init({ contextStorage:{'abc-123':{module:testPlugin}}});
|
||||
Context.load().then(function () {
|
||||
done("An error was not thrown");
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail when using invalid default context', function(done) {
|
||||
Context.init({contextStorage:{default:"noexist"}});
|
||||
Context.load().then(function(){
|
||||
done("An error was not thrown");
|
||||
}).catch(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail for the storage with no module', function(done) {
|
||||
Context.init({ contextStorage: { test: {}}});
|
||||
Context.load().then(function(){
|
||||
done("An error was not thrown");
|
||||
}).catch(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail to load non-existent module', function(done) {
|
||||
Context.init({contextStorage:{ file:{module:"nonexistent"} }});
|
||||
Context.load().then(function(){
|
||||
done("An error was not thrown");
|
||||
}).catch(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail to load invalid module', function (done) {
|
||||
Context.init({contextStorage: {
|
||||
test: {
|
||||
module: function (config) {
|
||||
throw new Error("invalid plugin was loaded.");
|
||||
}
|
||||
}
|
||||
}});
|
||||
Context.load().then(function () {
|
||||
done("An error was not thrown");
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('close modules',function(){
|
||||
it('should call close()', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
return Context.close().then(function(){
|
||||
stubClose.called.should.be.true();
|
||||
stubClose2.called.should.be.true();
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('store context',function() {
|
||||
it('should store local property to external context storage',function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
var cb = function(){done("An error occurred")}
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set("foo","bar","test",cb);
|
||||
context.get("foo","test",cb);
|
||||
context.keys("test",cb);
|
||||
stubSet.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
|
||||
stubGet.calledWith("1:flow","foo").should.be.true();
|
||||
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should store flow property to external context storage',function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.flow.set("foo","bar","test",cb);
|
||||
context.flow.get("foo","test",cb);
|
||||
context.flow.keys("test",cb);
|
||||
stubSet.calledWithExactly("flow","foo","bar",cb).should.be.true();
|
||||
stubGet.calledWith("flow","foo").should.be.true();
|
||||
stubKeys.calledWithExactly("flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should store global property to external context storage',function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.global.set("foo","bar","test",cb);
|
||||
context.global.get("foo","test",cb);
|
||||
context.global.keys("test",cb);
|
||||
stubSet.calledWithExactly("global","foo","bar",cb).should.be.true();
|
||||
stubGet.calledWith("global","foo").should.be.true();
|
||||
stubKeys.calledWith("global").should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should store data to the default context when non-existent context storage was specified', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.set("foo","bar","nonexist",cb);
|
||||
context.get("foo","nonexist",cb);
|
||||
context.keys("nonexist",cb);
|
||||
stubGet.called.should.be.false();
|
||||
stubSet.called.should.be.false();
|
||||
stubKeys.called.should.be.false();
|
||||
stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
|
||||
stubGet2.calledWith("1:flow","foo").should.be.true();
|
||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should use the default context', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.set("foo","bar","default",cb);
|
||||
context.get("foo","default",cb);
|
||||
context.keys("default",cb);
|
||||
stubGet.called.should.be.false();
|
||||
stubSet.called.should.be.false();
|
||||
stubKeys.called.should.be.false();
|
||||
stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
|
||||
stubGet2.calledWith("1:flow","foo").should.be.true();
|
||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should use the alias of default context', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.set("foo","alias",cb);
|
||||
context.get("foo",cb);
|
||||
context.keys(cb);
|
||||
stubGet.called.should.be.false();
|
||||
stubSet.called.should.be.false();
|
||||
stubKeys.called.should.be.false();
|
||||
stubSet2.calledWithExactly("1:flow","foo","alias",cb).should.be.true();
|
||||
stubGet2.calledWith("1:flow","foo").should.be.true();
|
||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should use default as the alias of other context', function(done) {
|
||||
Context.init({contextStorage:contextAlias});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.set("foo","alias",cb);
|
||||
context.get("foo",cb);
|
||||
context.keys(cb);
|
||||
stubSet.calledWithExactly("1:flow","foo","alias",cb).should.be.true();
|
||||
stubGet.calledWith("1:flow","foo").should.be.true();
|
||||
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it('should not throw an error using undefined storage for local context', function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.get("local","nonexist",cb);
|
||||
done()
|
||||
}).catch(done);
|
||||
});
|
||||
it('should throw an error using undefined storage for flow context', function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
var cb = function(){done("An error occurred")}
|
||||
context.flow.get("flow","nonexist",cb);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should return functionGlobalContext value as a default - synchronous', function(done) {
|
||||
var fGC = { "foo": 456 };
|
||||
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
|
||||
Context.load().then(function() {
|
||||
var context = Context.get("1","flow");
|
||||
// Get foo - should be value from fGC
|
||||
var v = context.global.get("foo");
|
||||
v.should.equal(456);
|
||||
|
||||
// Update foo - should not touch fGC object
|
||||
context.global.set("foo","new value");
|
||||
fGC.foo.should.equal(456);
|
||||
|
||||
// Get foo - should be the updated value
|
||||
v = context.global.get("foo");
|
||||
v.should.equal("new value");
|
||||
done();
|
||||
}).catch(done);
|
||||
})
|
||||
|
||||
it('should return functionGlobalContext value as a default - async', function(done) {
|
||||
var fGC = { "foo": 456 };
|
||||
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
|
||||
Context.load().then(function() {
|
||||
var context = Context.get("1","flow");
|
||||
// Get foo - should be value from fGC
|
||||
context.global.get("foo", function(err, v) {
|
||||
if (err) {
|
||||
done(err)
|
||||
} else {
|
||||
v.should.equal(456);
|
||||
// Update foo - should not touch fGC object
|
||||
context.global.set("foo","new value", function(err) {
|
||||
if (err) {
|
||||
done(err)
|
||||
} else {
|
||||
fGC.foo.should.equal(456);
|
||||
// Get foo - should be the updated value
|
||||
context.global.get("foo", function(err, v) {
|
||||
if (err) {
|
||||
done(err)
|
||||
} else {
|
||||
v.should.equal("new value");
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(done);
|
||||
})
|
||||
|
||||
it('should return multiple values if key is an array', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set("foo1","bar1","memory");
|
||||
context.set("foo2","bar2","memory");
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
foo2.should.be.equal("bar2");
|
||||
should.not.exist(foo3);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}).catch(function(err){ done(err); });
|
||||
});
|
||||
|
||||
it('should return multiple functionGlobalContext values if key is an array', function(done) {
|
||||
var fGC = { "foo1": 456, "foo2": 789 };
|
||||
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal(456);
|
||||
foo2.should.be.equal(789);
|
||||
should.not.exist(foo3);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}).catch(function(err){ done(err); });
|
||||
});
|
||||
|
||||
it('should return an error if an error occurs in getting multiple store values', function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
stubGet.onFirstCall().callsArgWith(2, "error2", "bar1");
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err === "error2") {
|
||||
done();
|
||||
} else {
|
||||
done("An error occurred");
|
||||
}
|
||||
});
|
||||
}).catch(function(err){ done(err); });
|
||||
});
|
||||
|
||||
it('should return a first error if some errors occur in getting multiple store values', function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
stubGet.onFirstCall().callsArgWith(2, "error1");
|
||||
stubGet.onSecondCall().callsArgWith(2, null, "bar2");
|
||||
stubGet.onThirdCall().callsArgWith(2, "error3");
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err === "error1") {
|
||||
done();
|
||||
} else {
|
||||
done("An error occurred");
|
||||
}
|
||||
});
|
||||
}).catch(function(err){ done(err); });
|
||||
});
|
||||
|
||||
it('should store multiple properties if key and value are arrays', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
foo2.should.be.equal("bar2");
|
||||
foo3.should.be.equal("bar3");
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should deletes multiple properties', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
foo2.should.be.equal("bar2");
|
||||
foo3.should.be.equal("bar3");
|
||||
context.set(["foo1","foo2","foo3"], new Array(3), "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
should.not.exist(foo1);
|
||||
should.not.exist(foo2);
|
||||
should.not.exist(foo3);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use null for missing values if the value array is shorter than the key array', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], ["bar1","bar2"], "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.keys(function(err, keys){
|
||||
keys.should.have.length(3);
|
||||
keys.should.eql(["foo1","foo2","foo3"]);
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
foo2.should.be.equal("bar2");
|
||||
should(foo3).be.null();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use null for missing values if the value is not array', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], "bar1", "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.keys(function(err, keys){
|
||||
keys.should.have.length(3);
|
||||
keys.should.eql(["foo1","foo2","foo3"]);
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
should(foo2).be.null();
|
||||
should(foo3).be.null();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore the extra values if the value array is longer than the key array', function(done) {
|
||||
Context.init({contextStorage:memoryStorage});
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3","ignored"], "memory", function(err){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
context.keys(function(err, keys){
|
||||
keys.should.have.length(3);
|
||||
keys.should.eql(["foo1","foo2","foo3"]);
|
||||
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
foo1.should.be.equal("bar1");
|
||||
foo2.should.be.equal("bar2");
|
||||
foo3.should.be.equal("bar3");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if an error occurs in storing multiple values', function(done) {
|
||||
Context.init({contextStorage:contextStorage});
|
||||
stubSet.onFirstCall().callsArgWith(3, "error2");
|
||||
Context.load().then(function(){
|
||||
var context = Context.get("1","flow");
|
||||
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
|
||||
if (err === "error2") {
|
||||
done();
|
||||
} else {
|
||||
done("An error occurred");
|
||||
}
|
||||
});
|
||||
}).catch(function(err){ done(err); });
|
||||
});
|
||||
|
||||
it('should throw an error if callback of context.get is not a function', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.get("foo", "memory", "callback");
|
||||
done("should throw an error.");
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error if callback of context.get is not specified', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.get("foo", "memory");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should throw an error if callback of context.set is not a function', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.set("foo", "bar", "memory", "callback");
|
||||
done("should throw an error.");
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error if callback of context.set is not specified', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.set("foo", "bar", "memory");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should throw an error if callback of context.keys is not a function', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.keys("memory", "callback");
|
||||
done("should throw an error.");
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error if callback of context.keys is not specified', function (done) {
|
||||
Context.init({ contextStorage: memoryStorage });
|
||||
Context.load().then(function () {
|
||||
var context = Context.get("1", "flow");
|
||||
context.keys("memory");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('listStores', function () {
|
||||
it('should list context storages', function (done) {
|
||||
Context.init({ contextStorage: contextDefaultStorage });
|
||||
Context.load().then(function () {
|
||||
var list = Context.listStores();
|
||||
list.default.should.equal("default");
|
||||
list.stores.should.eql(["default", "test"]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should list context storages without default storage', function (done) {
|
||||
Context.init({ contextStorage: contextStorage });
|
||||
Context.load().then(function () {
|
||||
var list = Context.listStores();
|
||||
list.default.should.equal("test");
|
||||
list.stores.should.eql(["test"]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete context',function(){
|
||||
it('should not call delete() when external context storage is used', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
Context.get("flowA");
|
||||
return Context.delete("flowA").then(function(){
|
||||
stubDelete.called.should.be.false();
|
||||
stubDelete2.called.should.be.false();
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clean context',function(){
|
||||
it('should call clean()', function(done) {
|
||||
Context.init({contextStorage:contextDefaultStorage});
|
||||
Context.load().then(function(){
|
||||
return Context.clean({allNodes:{}}).then(function(){
|
||||
stubClean.calledWithExactly([]).should.be.true();
|
||||
stubClean2.calledWithExactly([]).should.be.true();
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,882 @@
|
||||
/**
|
||||
* 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 LocalFileSystem = require('../../../../../red/runtime/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"};
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
319
test/unit/@node-red/runtime/lib/nodes/context/memory_spec.js
Normal file
319
test/unit/@node-red/runtime/lib/nodes/context/memory_spec.js
Normal file
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* 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 Memory = require('../../../../../red/runtime/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"]);
|
||||
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"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
477
test/unit/@node-red/runtime/lib/nodes/credentials_spec.js
Normal file
477
test/unit/@node-red/runtime/lib/nodes/credentials_spec.js
Normal file
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var util = require("util");
|
||||
|
||||
var index = require("../../../../red/runtime/nodes/index");
|
||||
var credentials = require("../../../../red/runtime/nodes/credentials");
|
||||
var log = require("../../../../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(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('adds a new credential',function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
credentials.load({"a":{"b":1,"c":2}}).then(function() {
|
||||
credentials.dirty().should.be.false();
|
||||
should.not.exist(credentials.get("b"));
|
||||
credentials.add("b",{"foo":"bar"}).then(function() {
|
||||
credentials.get("b").should.have.property("foo","bar");
|
||||
credentials.dirty().should.be.true();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('deletes an existing credential',function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
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();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('exports the credentials, clearing dirty flag', function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
var creds = {"a":{"b":1,"c":2}};
|
||||
credentials.load(creds).then(function() {
|
||||
credentials.add("b",{"foo":"bar"}).then(function() {
|
||||
credentials.dirty().should.be.true();
|
||||
credentials.export().then(function(exported) {
|
||||
exported.should.eql(creds);
|
||||
credentials.dirty().should.be.false();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe("#clean",function() {
|
||||
it("removes credentials of unknown nodes",function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
var creds = {"a":{"b":1,"c":2},"b":{"d":3}};
|
||||
credentials.load(creds).then(function() {
|
||||
credentials.dirty().should.be.false();
|
||||
should.exist(credentials.get("a"));
|
||||
should.exist(credentials.get("b"));
|
||||
credentials.clean([{id:"b"}]).then(function() {
|
||||
credentials.dirty().should.be.true();
|
||||
should.not.exist(credentials.get("a"));
|
||||
should.exist(credentials.get("b"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("extracts credentials of known nodes",function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
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"}}];
|
||||
credentials.load(creds).then(function() {
|
||||
credentials.dirty().should.be.false();
|
||||
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);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('warns if a node has no credential definition', function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
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();
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('extract credential updates in the provided node', function(done) {
|
||||
credentials.init({
|
||||
log: log,
|
||||
settings: encryptionDisabledSettings
|
||||
});
|
||||
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
|
||||
});
|
||||
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 when.resolve();
|
||||
},
|
||||
delete: function(key) {
|
||||
delete settings[key];
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
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(done) {
|
||||
settings = {
|
||||
_credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
|
||||
credentialSecret: "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
|
||||
};
|
||||
// {"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.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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
// {"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();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
1106
test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js
Normal file
1106
test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
581
test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js
Normal file
581
test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var clone = require("clone");
|
||||
var flows = require("../../../../../red/runtime/nodes/flows");
|
||||
var RedNode = require("../../../../../red/runtime/nodes/Node");
|
||||
var RED = require("../../../../../red/runtime/nodes");
|
||||
var events = require("../../../../../red/runtime/events");
|
||||
var credentials = require("../../../../../red/runtime/nodes/credentials");
|
||||
var typeRegistry = require("../../../../../red/runtime-registry");
|
||||
var Flow = require("../../../../../red/runtime/nodes/flows/Flow");
|
||||
|
||||
describe('flows/index', function() {
|
||||
|
||||
var storage;
|
||||
var eventsOn;
|
||||
var credentialsClean;
|
||||
var credentialsLoad;
|
||||
|
||||
var flowCreate;
|
||||
var getType;
|
||||
|
||||
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",function(type) {
|
||||
return type.indexOf('missing') === -1;
|
||||
});
|
||||
});
|
||||
after(function() {
|
||||
getType.restore();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
eventsOn = sinon.spy(events,"on");
|
||||
credentialsClean = sinon.stub(credentials,"clean",function(conf) {
|
||||
conf.forEach(function(n) {
|
||||
delete n.credentials;
|
||||
});
|
||||
return when.resolve();
|
||||
});
|
||||
credentialsLoad = sinon.stub(credentials,"load",function() {
|
||||
return when.resolve();
|
||||
});
|
||||
flowCreate = sinon.stub(Flow,"create",function(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 when.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
eventsOn.restore();
|
||||
credentialsClean.restore();
|
||||
credentialsLoad.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 when.resolve(456);
|
||||
},
|
||||
getFlows: function() {
|
||||
return when.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('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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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 when.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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-started',function() {
|
||||
Object.keys(flowCreate.flows).should.eql(['_GLOBAL_','t1']);
|
||||
done();
|
||||
});
|
||||
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
flows.load().then(function() {
|
||||
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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
flows.load().then(function() {
|
||||
flows.startFlows();
|
||||
flowCreate.called.should.be.false();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
flows.load().then(function() {
|
||||
flows.startFlows();
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
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 when.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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
|
||||
events.once('nodes-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",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 when.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 when.resolve({flows:originalConfig});
|
||||
}
|
||||
storage.setFlows = function() {
|
||||
return when.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");
|
||||
})
|
||||
});
|
737
test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js
Normal file
737
test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js
Normal file
@@ -0,0 +1,737 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var clone = require("clone");
|
||||
var flowUtil = require("../../../../../red/runtime/nodes/flows/util");
|
||||
var typeRegistry = require("../../../../../red/runtime-registry");
|
||||
var redUtil = require("../../../../../red/runtime/util");
|
||||
|
||||
describe('flows/util', function() {
|
||||
var getType;
|
||||
|
||||
before(function() {
|
||||
getType = sinon.stub(typeRegistry,"get",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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
|
||||
});
|
||||
});
|
||||
|
||||
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":[]}}}},"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":[]}}}},"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":[]}}}},"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"}}}},"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: [] }}}},"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":[]}}}},"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);
|
||||
});
|
||||
});
|
||||
});
|
403
test/unit/@node-red/runtime/lib/nodes/index_spec.js
Normal file
403
test/unit/@node-red/runtime/lib/nodes/index_spec.js
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var sinon = require('sinon');
|
||||
var inherits = require("util").inherits;
|
||||
|
||||
var index = require("../../../../red/runtime/nodes/index");
|
||||
var flows = require("../../../../red/runtime/nodes/flows");
|
||||
var registry = require("../../../../red/runtime-registry");
|
||||
var Node = require("../../../../red/runtime/nodes/Node");
|
||||
|
||||
describe("red/nodes/index", function() {
|
||||
before(function() {
|
||||
sinon.stub(index,"startFlows");
|
||||
process.env.NODE_RED_HOME = path.resolve(path.join(__dirname,"..","..","..",".."))
|
||||
});
|
||||
after(function() {
|
||||
index.startFlows.restore();
|
||||
delete process.env.NODE_RED_HOME;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
index.clearRegistry();
|
||||
});
|
||||
|
||||
process.env.foo="bar";
|
||||
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
||||
var testCredentials = {"tab1":{"b":1, "c":"2", "d":"$(foo)"}};
|
||||
var storage = {
|
||||
getFlows: function() {
|
||||
return when({red:123,flows:testFlows,credentials:testCredentials});
|
||||
},
|
||||
saveFlows: function(conf) {
|
||||
should.deepEqual(testFlows, conf.flows);
|
||||
return when.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) {
|
||||
index.createNode(this, n);
|
||||
var node = this;
|
||||
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 = require("../../../../red/runtime");
|
||||
var credentials = require("../../../../red/runtime/nodes/credentials");
|
||||
var localfilesystem = require("../../../../red/runtime/storage/localfilesystem");
|
||||
var log = require("../../../../red/util/log");
|
||||
var RED = require("../../../../red/red.js");
|
||||
|
||||
var userDir = path.join(__dirname,".testUserHome");
|
||||
before(function(done) {
|
||||
sinon.stub(log,"log",function(){});
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,function() {
|
||||
sinon.stub(index, 'load', function() {
|
||||
return when.promise(function(resolve,reject){
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
sinon.stub(localfilesystem, 'getCredentials', function() {
|
||||
return when.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",function(id) {
|
||||
if (id == "test") {
|
||||
return {id:"1234",types:["test"]};
|
||||
} else if (id == "doesnotexist") {
|
||||
return null;
|
||||
} else {
|
||||
return randomNodeInfo;
|
||||
}
|
||||
});
|
||||
sinon.stub(registry,"disableNode",function(id) {
|
||||
return when.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",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",function(module) {
|
||||
if (module == "node-red") {
|
||||
return {nodes:[{name:"foo"}]};
|
||||
} else if (module == "doesnotexist") {
|
||||
return null;
|
||||
} else {
|
||||
return randomModuleInfo;
|
||||
}
|
||||
});
|
||||
sinon.stub(registry,"removeModule",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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 393 B |
3
test/unit/@node-red/runtime/lib/nodes/resources/local/TestNodeModule/node_modules/TestNodeModule/icons/arrow-in.png
generated
vendored
Normal file
3
test/unit/@node-red/runtime/lib/nodes/resources/local/TestNodeModule/node_modules/TestNodeModule/icons/arrow-in.png
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
242
test/unit/@node-red/runtime/lib/settings_spec.js
Normal file
242
test/unit/@node-red/runtime/lib/settings_spec.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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 settings = require("../../../red/runtime/settings");
|
||||
|
||||
|
||||
describe("red/settings", function() {
|
||||
|
||||
afterEach(function() {
|
||||
settings.reset();
|
||||
});
|
||||
|
||||
it('wraps the user settings as read-only properties', function() {
|
||||
var userSettings = {
|
||||
a: 123,
|
||||
b: "test",
|
||||
c: [1,2,3]
|
||||
}
|
||||
settings.init(userSettings);
|
||||
|
||||
settings.available().should.be.false();
|
||||
|
||||
settings.a.should.equal(123);
|
||||
settings.b.should.equal("test");
|
||||
settings.c.should.be.an.Array();
|
||||
settings.c.should.have.lengthOf(3);
|
||||
|
||||
settings.get("a").should.equal(123);
|
||||
settings.get("b").should.equal("test");
|
||||
settings.get("c").should.be.an.Array();
|
||||
settings.get("c").should.have.lengthOf(3);
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.a = 456;
|
||||
}).should.throw();
|
||||
|
||||
settings.c.push(5);
|
||||
settings.c.should.be.an.Array();
|
||||
settings.c.should.have.lengthOf(4);
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("a",456);
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("a",456);
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.get("unknown");
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("unknown",456);
|
||||
}).should.throw();
|
||||
|
||||
});
|
||||
|
||||
it('loads global settings from storage', function(done) {
|
||||
var userSettings = {
|
||||
a: 123,
|
||||
b: "test",
|
||||
c: [1,2,3]
|
||||
}
|
||||
var savedSettings = null;
|
||||
var saveCount = 0;
|
||||
var storage = {
|
||||
getSettings: function() {
|
||||
return Promise.resolve({globalA:789});
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
saveCount++;
|
||||
savedSettings = settings;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
settings.init(userSettings);
|
||||
|
||||
settings.available().should.be.false();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.get("unknown");
|
||||
}).should.throw();
|
||||
settings.load(storage).then(function() {
|
||||
settings.available().should.be.true();
|
||||
settings.get("globalA").should.equal(789);
|
||||
settings.set("globalA","abc").then(function() {
|
||||
savedSettings.globalA.should.equal("abc");
|
||||
saveCount.should.equal(1);
|
||||
settings.set("globalA","abc").then(function() {
|
||||
savedSettings.globalA.should.equal("abc");
|
||||
// setting to existing value should not trigger save
|
||||
saveCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes persistent settings when reset', function() {
|
||||
var userSettings = {
|
||||
a: 123,
|
||||
b: "test",
|
||||
c: [1,2,3]
|
||||
}
|
||||
settings.init(userSettings);
|
||||
|
||||
settings.available().should.be.false();
|
||||
|
||||
settings.should.have.property("a",123);
|
||||
settings.should.have.property("b","test");
|
||||
settings.c.should.be.an.Array();
|
||||
settings.c.should.have.lengthOf(3);
|
||||
|
||||
settings.reset();
|
||||
|
||||
settings.should.not.have.property("a");
|
||||
settings.should.not.have.property("d");
|
||||
settings.should.not.have.property("c");
|
||||
|
||||
});
|
||||
|
||||
it('registers node settings and exports them', function() {
|
||||
var userSettings = {};
|
||||
settings.init(userSettings);
|
||||
settings.registerNodeSettings("inject", {injectColor:{value:"red", exportable:true}, injectSize:{value:"100", exportable:true}} );
|
||||
settings.registerNodeSettings("mqtt", {mqttColor:{value:"purple", exportable:false}, mqttSize:{value:"50", exportable:true}} );
|
||||
settings.registerNodeSettings("http request", {httpRequest1:{value:"a1", exportable:true}} );
|
||||
settings.registerNodeSettings(" http--request<> ", {httpRequest2:{value:"a2", exportable:true}} );
|
||||
settings.registerNodeSettings("_http_request_", {httpRequest3:{value:"a3", exportable:true}} );
|
||||
settings.registerNodeSettings("mQtT", {mQtTColor:{value:"purple", exportable:true}} );
|
||||
settings.registerNodeSettings("abc123", {abc123:{value:"def456", exportable:true}} );
|
||||
settings.registerNodeSettings("noValue", {noValueHasValue:{value:"123", exportable:true}, noValueNoValue:{exportable:true}} );
|
||||
|
||||
var safeSettings = {};
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.have.property("injectColor", "red");
|
||||
safeSettings.should.have.property("injectSize", "100");
|
||||
safeSettings.should.not.have.property("mqttColor");
|
||||
safeSettings.should.have.property("mqttSize", "50");
|
||||
safeSettings.should.have.property("httpRequest1", "a1");
|
||||
safeSettings.should.have.property("httpRequest2", "a2");
|
||||
safeSettings.should.have.property("httpRequest3", "a3");
|
||||
safeSettings.should.have.property("mQtTColor", "purple");
|
||||
safeSettings.should.have.property("abc123", "def456");
|
||||
|
||||
safeSettings.should.have.property("noValueHasValue", "123");
|
||||
safeSettings.should.not.have.property("noValueNoValue");
|
||||
});
|
||||
|
||||
it('prohibits registering the property whose name do not start with type name', function() {
|
||||
var userSettings = {};
|
||||
settings.init(userSettings);
|
||||
(function() {
|
||||
settings.registerNodeSettings("inject", {color:{value:"red", exportable:true}} );
|
||||
}).should.throw();
|
||||
(function() {
|
||||
settings.registerNodeSettings("_a_b_1_", {ab1Color:{value:"red", exportable:true}} );
|
||||
}).should.throw();
|
||||
(function() {
|
||||
settings.registerNodeSettings("AB2", {AB2Color:{value:"red", exportable:true}} );
|
||||
}).should.throw();
|
||||
(function() {
|
||||
settings.registerNodeSettings("abcDef", {abcColor:{value:"red", exportable:true}} );
|
||||
}).should.throw();
|
||||
var safeSettings = {};
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.not.have.property("color");
|
||||
safeSettings.should.not.have.property("ab1Color", "blue");
|
||||
safeSettings.should.not.have.property("AB2Color");
|
||||
safeSettings.should.not.have.property("abcColor");
|
||||
});
|
||||
|
||||
it('overwrites node settings with user settings', function() {
|
||||
var userSettings = {
|
||||
injectColor: "green",
|
||||
mqttColor: "yellow",
|
||||
abColor: [1,2,3]
|
||||
}
|
||||
settings.init(userSettings);
|
||||
settings.registerNodeSettings("inject", {injectColor:{value:"red", exportable:true}} );
|
||||
settings.registerNodeSettings("ab", {abColor:{value:"red", exportable:false}} );
|
||||
var safeSettings = {};
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.have.property("injectColor", "green");
|
||||
safeSettings.should.not.have.property("mqttColor");
|
||||
safeSettings.should.not.have.property("abColor");
|
||||
});
|
||||
|
||||
it('disables/enables node settings', function() {
|
||||
var userSettings = {};
|
||||
settings.init(userSettings);
|
||||
|
||||
var safeSettings = {};
|
||||
settings.registerNodeSettings("inject", {injectColor:{value:"red", exportable:true}} );
|
||||
settings.registerNodeSettings("mqtt", {mqttColor:{value:"purple", exportable:true}} );
|
||||
settings.registerNodeSettings("http request", {httpRequestColor:{value:"yellow", exportable:true}} );
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.have.property("injectColor", "red");
|
||||
safeSettings.should.have.property("mqttColor", "purple");
|
||||
safeSettings.should.have.property("httpRequestColor", "yellow");
|
||||
|
||||
safeSettings = {};
|
||||
var types = ["inject", "mqtt"];
|
||||
settings.disableNodeSettings(types);
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.not.have.property("injectColor");
|
||||
safeSettings.should.not.have.property("mqttColor");
|
||||
safeSettings.should.have.property("httpRequestColor", "yellow");
|
||||
|
||||
safeSettings = {};
|
||||
types = ["inject"];
|
||||
settings.enableNodeSettings(types);
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
safeSettings.should.have.property("injectColor", "red");
|
||||
safeSettings.should.not.have.property("mqttColor");
|
||||
safeSettings.should.have.property("httpRequestColor", "yellow");
|
||||
});
|
||||
|
||||
});
|
269
test/unit/@node-red/runtime/lib/storage/index_spec.js
Normal file
269
test/unit/@node-red/runtime/lib/storage/index_spec.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var should = require("should");
|
||||
var paff = require('path');
|
||||
var storage = require("../../../../red/runtime/storage/index");
|
||||
|
||||
describe("red/storage/index", function() {
|
||||
|
||||
it('rejects the promise when settings suggest loading a bad module', function(done) {
|
||||
|
||||
var wrongModule = {
|
||||
settings:{
|
||||
storageModule : "thisaintloading"
|
||||
}
|
||||
};
|
||||
|
||||
storage.init(wrongModule).then( function() {
|
||||
var one = 1;
|
||||
var zero = 0;
|
||||
try {
|
||||
zero.should.equal(one, "The initialization promise should never get resolved");
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
}).catch(function(e) {
|
||||
done(); //successfully rejected promise
|
||||
});
|
||||
});
|
||||
|
||||
it('non-string storage module', function(done) {
|
||||
var initSetsMeToTrue = false;
|
||||
|
||||
var moduleWithBooleanSettingInit = {
|
||||
init : function() {
|
||||
initSetsMeToTrue = true;
|
||||
}
|
||||
};
|
||||
|
||||
var setsBooleanModule = {
|
||||
settings: {
|
||||
storageModule : moduleWithBooleanSettingInit
|
||||
}
|
||||
};
|
||||
|
||||
storage.init(setsBooleanModule);
|
||||
initSetsMeToTrue.should.be.true();
|
||||
done();
|
||||
});
|
||||
|
||||
it('respects storage interface', function(done) {
|
||||
var calledFlagGetFlows = false;
|
||||
var calledFlagGetCredentials = false;
|
||||
var calledFlagGetAllFlows = false;
|
||||
var calledInit = false;
|
||||
var calledFlagGetSettings = false;
|
||||
var calledFlagGetSessions = false;
|
||||
|
||||
var interfaceCheckerModule = {
|
||||
init : function (settings) {
|
||||
settings.should.be.an.Object();
|
||||
calledInit = true;
|
||||
},
|
||||
getFlows : function() {
|
||||
calledFlagGetFlows = true;
|
||||
return when.resolve([]);
|
||||
},
|
||||
saveFlows : function (flows) {
|
||||
flows.should.be.an.Array();
|
||||
flows.should.have.lengthOf(0);
|
||||
return when.resolve("");
|
||||
},
|
||||
getCredentials : function() {
|
||||
calledFlagGetCredentials = true;
|
||||
return when.resolve({});
|
||||
},
|
||||
saveCredentials : function(credentials) {
|
||||
credentials.should.be.true();
|
||||
},
|
||||
getSettings : function() {
|
||||
calledFlagGetSettings = true;
|
||||
},
|
||||
saveSettings : function(settings) {
|
||||
settings.should.be.true();
|
||||
},
|
||||
getSessions : function() {
|
||||
calledFlagGetSessions = true;
|
||||
},
|
||||
saveSessions : function(sessions) {
|
||||
sessions.should.be.true();
|
||||
},
|
||||
getAllFlows : function() {
|
||||
calledFlagGetAllFlows = true;
|
||||
},
|
||||
getFlow : function(fn) {
|
||||
fn.should.equal("name");
|
||||
},
|
||||
saveFlow : function(fn, data) {
|
||||
fn.should.equal("name");
|
||||
data.should.be.true();
|
||||
},
|
||||
getLibraryEntry : function(type, path) {
|
||||
type.should.be.true();
|
||||
path.should.equal("name");
|
||||
},
|
||||
saveLibraryEntry : function(type, path, meta, body) {
|
||||
type.should.be.true();
|
||||
path.should.equal("name");
|
||||
meta.should.be.true();
|
||||
body.should.be.true();
|
||||
}
|
||||
};
|
||||
|
||||
var moduleToLoad = {
|
||||
settings: {
|
||||
storageModule : interfaceCheckerModule
|
||||
}
|
||||
};
|
||||
|
||||
var promises = [];
|
||||
storage.init(moduleToLoad);
|
||||
promises.push(storage.getFlows());
|
||||
promises.push(storage.saveFlows({flows:[],credentials:{}}));
|
||||
storage.getSettings();
|
||||
storage.saveSettings(true);
|
||||
storage.getSessions();
|
||||
storage.saveSessions(true);
|
||||
storage.getAllFlows();
|
||||
storage.getFlow("name");
|
||||
storage.saveFlow("name", true);
|
||||
storage.getLibraryEntry(true, "name");
|
||||
storage.saveLibraryEntry(true, "name", true, true);
|
||||
|
||||
when.settle(promises).then(function() {
|
||||
try {
|
||||
calledInit.should.be.true();
|
||||
calledFlagGetFlows.should.be.true();
|
||||
calledFlagGetCredentials.should.be.true();
|
||||
calledFlagGetAllFlows.should.be.true();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('respects deprecated flow library functions', function() {
|
||||
|
||||
var savePath;
|
||||
var saveContent;
|
||||
var saveMeta;
|
||||
var saveType;
|
||||
|
||||
var interfaceCheckerModule = {
|
||||
init : function (settings) {
|
||||
settings.should.be.an.Object();
|
||||
},
|
||||
getLibraryEntry : function(type, path) {
|
||||
if (type === "flows") {
|
||||
if (path === "/" || path === "\\") {
|
||||
return when.resolve(["a",{fn:"test.json"}]);
|
||||
} else if (path == "/a" || path == "\\a") {
|
||||
return when.resolve([{fn:"test2.json"}]);
|
||||
} else if (path == paff.join("","a","test2.json")) {
|
||||
return when.resolve("test content");
|
||||
}
|
||||
}
|
||||
},
|
||||
saveLibraryEntry : function(type, path, meta, body) {
|
||||
saveType = type;
|
||||
savePath = path;
|
||||
saveContent = body;
|
||||
saveMeta = meta;
|
||||
return when.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
var moduleToLoad = {
|
||||
settings: {
|
||||
storageModule : interfaceCheckerModule
|
||||
}
|
||||
};
|
||||
before(function() {
|
||||
storage.init(moduleToLoad);
|
||||
});
|
||||
it('getAllFlows',function(done) {
|
||||
storage.getAllFlows().then(function (res) {
|
||||
try {
|
||||
res.should.eql({ d: { a: { f: ['test2'] } }, f: [ 'test' ] });
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('getFlow',function(done) {
|
||||
storage.getFlow(paff.join("a","test2.json")).then(function(res) {
|
||||
try {
|
||||
res.should.eql("test content");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it ('saveFlow', function (done) {
|
||||
storage.saveFlow(paff.join("a","test2.json"),"new content").then(function(res) {
|
||||
try {
|
||||
savePath.should.eql(paff.join("a","test2.json"));
|
||||
saveContent.should.eql("new content");
|
||||
saveMeta.should.eql({});
|
||||
saveType.should.eql("flows");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles missing settings/sessions interface', function() {
|
||||
before(function() {
|
||||
var interfaceCheckerModule = {
|
||||
init : function () {}
|
||||
};
|
||||
storage.init({settings:{storageModule: interfaceCheckerModule}});
|
||||
});
|
||||
|
||||
it('defaults missing getSettings',function(done) {
|
||||
storage.getSettings().then(function(settings) {
|
||||
should.not.exist(settings);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('defaults missing saveSettings',function(done) {
|
||||
storage.saveSettings({}).then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('defaults missing getSessions',function(done) {
|
||||
storage.getSessions().then(function(settings) {
|
||||
should.not.exist(settings);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('defaults missing saveSessions',function(done) {
|
||||
storage.saveSessions({}).then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,476 @@
|
||||
/**
|
||||
* 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 localfilesystem = require("../../../../../red/runtime/storage/localfilesystem");
|
||||
var log = require("../../../../../red/util/log");
|
||||
|
||||
describe('storage/localfilesystem', function() {
|
||||
var mockRuntime = {
|
||||
log:{
|
||||
_:function() { return "placeholder message"},
|
||||
info: function() { },
|
||||
warn: function() { },
|
||||
trace: function() {}
|
||||
}
|
||||
};
|
||||
var userDir = path.join(__dirname,".testUserHome");
|
||||
var testFlow = [{"type":"tab","id":"d8be2a6d.2741d8","label":"Sheet 1"}];
|
||||
beforeEach(function(done) {
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,done);
|
||||
});
|
||||
});
|
||||
afterEach(function(done) {
|
||||
fs.remove(userDir,done);
|
||||
});
|
||||
|
||||
it('should initialise the user directory',function(done) {
|
||||
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() {
|
||||
fs.existsSync(path.join(userDir,"lib")).should.be.true();
|
||||
fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should set userDir to NRH if .config.json presents',function(done) {
|
||||
var oldNRH = process.env.NODE_RED_HOME;
|
||||
process.env.NODE_RED_HOME = path.join(userDir,"NRH");
|
||||
fs.mkdirSync(process.env.NODE_RED_HOME);
|
||||
fs.writeFileSync(path.join(process.env.NODE_RED_HOME,".config.json"),"{}","utf8");
|
||||
var settings = {};
|
||||
localfilesystem.init(settings, mockRuntime).then(function() {
|
||||
try {
|
||||
fs.existsSync(path.join(process.env.NODE_RED_HOME,"lib")).should.be.true();
|
||||
fs.existsSync(path.join(process.env.NODE_RED_HOME,"lib",'flows')).should.be.true();
|
||||
settings.userDir.should.equal(process.env.NODE_RED_HOME);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
} finally {
|
||||
process.env.NODE_RED_HOME = oldNRH;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set userDir to HOMEPATH/.node-red if .config.json presents',function(done) {
|
||||
var oldNRH = process.env.NODE_RED_HOME;
|
||||
process.env.NODE_RED_HOME = path.join(userDir,"NRH");
|
||||
var oldHOMEPATH = process.env.HOMEPATH;
|
||||
process.env.HOMEPATH = path.join(userDir,"HOMEPATH");
|
||||
fs.mkdirSync(process.env.HOMEPATH);
|
||||
fs.mkdirSync(path.join(process.env.HOMEPATH,".node-red"));
|
||||
fs.writeFileSync(path.join(process.env.HOMEPATH,".node-red",".config.json"),"{}","utf8");
|
||||
var settings = {};
|
||||
localfilesystem.init(settings, mockRuntime).then(function() {
|
||||
try {
|
||||
fs.existsSync(path.join(process.env.HOMEPATH,".node-red","lib")).should.be.true();
|
||||
fs.existsSync(path.join(process.env.HOMEPATH,".node-red","lib",'flows')).should.be.true();
|
||||
settings.userDir.should.equal(path.join(process.env.HOMEPATH,".node-red"));
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
} finally {
|
||||
process.env.NODE_RED_HOME = oldNRH;
|
||||
process.env.NODE_HOMEPATH = oldHOMEPATH;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set userDir to HOME/.node-red',function(done) {
|
||||
var oldNRH = process.env.NODE_RED_HOME;
|
||||
process.env.NODE_RED_HOME = path.join(userDir,"NRH");
|
||||
var oldHOME = process.env.HOME;
|
||||
process.env.HOME = path.join(userDir,"HOME");
|
||||
var oldHOMEPATH = process.env.HOMEPATH;
|
||||
process.env.HOMEPATH = path.join(userDir,"HOMEPATH");
|
||||
|
||||
fs.mkdirSync(process.env.HOME);
|
||||
var settings = {};
|
||||
localfilesystem.init(settings, mockRuntime).then(function() {
|
||||
try {
|
||||
fs.existsSync(path.join(process.env.HOME,".node-red","lib")).should.be.true();
|
||||
fs.existsSync(path.join(process.env.HOME,".node-red","lib",'flows')).should.be.true();
|
||||
settings.userDir.should.equal(path.join(process.env.HOME,".node-red"));
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
} finally {
|
||||
process.env.NODE_RED_HOME = oldNRH;
|
||||
process.env.HOME = oldHOME;
|
||||
process.env.HOMEPATH = oldHOMEPATH;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set userDir to USERPROFILE/.node-red',function(done) {
|
||||
var oldNRH = process.env.NODE_RED_HOME;
|
||||
process.env.NODE_RED_HOME = path.join(userDir,"NRH");
|
||||
var oldHOME = process.env.HOME;
|
||||
process.env.HOME = "";
|
||||
var oldHOMEPATH = process.env.HOMEPATH;
|
||||
process.env.HOMEPATH = path.join(userDir,"HOMEPATH");
|
||||
var oldUSERPROFILE = process.env.USERPROFILE;
|
||||
process.env.USERPROFILE = path.join(userDir,"USERPROFILE");
|
||||
|
||||
fs.mkdirSync(process.env.USERPROFILE);
|
||||
var settings = {};
|
||||
localfilesystem.init(settings, mockRuntime).then(function() {
|
||||
try {
|
||||
fs.existsSync(path.join(process.env.USERPROFILE,".node-red","lib")).should.be.true();
|
||||
fs.existsSync(path.join(process.env.USERPROFILE,".node-red","lib",'flows')).should.be.true();
|
||||
settings.userDir.should.equal(path.join(process.env.USERPROFILE,".node-red"));
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
} finally {
|
||||
process.env.NODE_RED_HOME = oldNRH;
|
||||
process.env.HOME = oldHOME;
|
||||
process.env.HOMEPATH = oldHOMEPATH;
|
||||
process.env.USERPROFILE = oldUSERPROFILE;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle missing flow file',function(done) {
|
||||
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
fs.existsSync(flowFilePath).should.be.false();
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty flow file, no backup',function(done) {
|
||||
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
fs.closeSync(fs.openSync(flowFilePath, 'w'));
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty flow file, restores backup',function(done) {
|
||||
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
fs.closeSync(fs.openSync(flowFilePath, 'w'));
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
fs.existsSync(flowFileBackupPath).should.be.false();
|
||||
fs.writeFileSync(flowFileBackupPath,JSON.stringify(testFlow));
|
||||
fs.existsSync(flowFileBackupPath).should.be.true();
|
||||
setTimeout(function() {
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql(testFlow);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
},50);
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should save flows to the default file',function(done) {
|
||||
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
fs.existsSync(flowFilePath).should.be.false();
|
||||
fs.existsSync(flowFileBackupPath).should.be.false();
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
fs.existsSync(flowFileBackupPath).should.be.false();
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql(testFlow);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should save flows to the specified file',function(done) {
|
||||
var defaultFlowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var defaultFlowFilePath = path.join(userDir,defaultFlowFile);
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
fs.existsSync(defaultFlowFilePath).should.be.false();
|
||||
fs.existsSync(flowFilePath).should.be.false();
|
||||
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.existsSync(defaultFlowFilePath).should.be.false();
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql(testFlow);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should format the flows file when flowFilePretty specified',function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath,flowFilePretty:true}, mockRuntime).then(function() {
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
var content = fs.readFileSync(flowFilePath,"utf8");
|
||||
content.split("\n").length.should.be.above(1);
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql(testFlow);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fsync the flows file',function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
localfilesystem.init({editorTheme:{projects:{enabled:false}},userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
sinon.spy(fs,"fsync");
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.fsync.callCount.should.be.greaterThan(0);
|
||||
fs.fsync.restore();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
fs.fsync.restore();
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should log fsync errors and continue',function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
sinon.stub(fs,"fsync", function(fd, cb) {
|
||||
cb(new Error());
|
||||
});
|
||||
sinon.spy(log,"warn");
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.fsync.callCount.should.be.greaterThan(0);
|
||||
log.warn.restore();
|
||||
fs.fsync.callCount.should.be.greaterThan(0);
|
||||
fs.fsync.restore();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should backup the flows file', function(done) {
|
||||
var defaultFlowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var defaultFlowFilePath = path.join(userDir,defaultFlowFile);
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
fs.existsSync(defaultFlowFilePath).should.be.false();
|
||||
fs.existsSync(flowFilePath).should.be.false();
|
||||
fs.existsSync(flowFileBackupPath).should.be.false();
|
||||
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.existsSync(flowFileBackupPath).should.be.false();
|
||||
fs.existsSync(defaultFlowFilePath).should.be.false();
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
var content = fs.readFileSync(flowFilePath,'utf8');
|
||||
var testFlow2 = [{"type":"tab","id":"bc5672ad.2741d8","label":"Sheet 2"}];
|
||||
|
||||
localfilesystem.saveFlows(testFlow2).then(function() {
|
||||
fs.existsSync(flowFileBackupPath).should.be.true();
|
||||
fs.existsSync(defaultFlowFilePath).should.be.false();
|
||||
fs.existsSync(flowFilePath).should.be.true();
|
||||
var backupContent = fs.readFileSync(flowFileBackupPath,'utf8');
|
||||
content.should.equal(backupContent);
|
||||
var content2 = fs.readFileSync(flowFilePath,'utf8');
|
||||
content2.should.not.equal(backupContent);
|
||||
done();
|
||||
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('should handle missing credentials', function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var credFile = path.join(userDir,"test_cred.json");
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
fs.existsSync(credFile).should.be.false();
|
||||
|
||||
localfilesystem.getCredentials().then(function(creds) {
|
||||
creds.should.eql({});
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle credentials', function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var credFile = path.join(userDir,"test_cred.json");
|
||||
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
|
||||
fs.existsSync(credFile).should.be.false();
|
||||
|
||||
var credentials = {"abc":{"type":"creds"}};
|
||||
|
||||
localfilesystem.saveCredentials(credentials).then(function() {
|
||||
fs.existsSync(credFile).should.be.true();
|
||||
localfilesystem.getCredentials().then(function(creds) {
|
||||
creds.should.eql(credentials);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should backup existing credentials', function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var credFile = path.join(userDir,"test_cred.json");
|
||||
var credFileBackup = path.join(userDir,".test_cred.json.backup");
|
||||
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() {
|
||||
|
||||
fs.writeFileSync(credFile,"{}","utf8");
|
||||
|
||||
fs.existsSync(credFile).should.be.true();
|
||||
fs.existsSync(credFileBackup).should.be.false();
|
||||
|
||||
var credentials = {"abc":{"type":"creds"}};
|
||||
|
||||
localfilesystem.saveCredentials(credentials).then(function() {
|
||||
fs.existsSync(credFile).should.be.true();
|
||||
fs.existsSync(credFileBackup).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should format the creds file when flowFilePretty specified',function(done) {
|
||||
var flowFile = 'test.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var credFile = path.join(userDir,"test_cred.json");
|
||||
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFilePath, flowFilePretty:true}, mockRuntime).then(function() {
|
||||
|
||||
fs.existsSync(credFile).should.be.false();
|
||||
|
||||
var credentials = {"abc":{"type":"creds"}};
|
||||
|
||||
localfilesystem.saveCredentials(credentials).then(function() {
|
||||
fs.existsSync(credFile).should.be.true();
|
||||
var content = fs.readFileSync(credFile,"utf8");
|
||||
content.split("\n").length.should.be.above(1);
|
||||
localfilesystem.getCredentials().then(function(creds) {
|
||||
creds.should.eql(credentials);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* 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 localfilesystemLibrary = require("../../../../../red/runtime/storage/localfilesystem/library");
|
||||
|
||||
describe('storage/localfilesystem/library', function() {
|
||||
var userDir = path.join(__dirname,".testUserHome");
|
||||
beforeEach(function(done) {
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,done);
|
||||
});
|
||||
});
|
||||
afterEach(function(done) {
|
||||
fs.remove(userDir,done);
|
||||
});
|
||||
|
||||
it('should return an empty list of library objects',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty list of library objects (path=/)',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
localfilesystemLibrary.getLibraryEntry('object','/').then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error for a non-existent library object',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
localfilesystemLibrary.getLibraryEntry('object','A/B').then(function(flows) {
|
||||
should.fail(null,null,"non-existent flow");
|
||||
}).catch(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
function createObjectLibrary(type) {
|
||||
type = type ||"object";
|
||||
var objLib = path.join(userDir,"lib",type);
|
||||
try {
|
||||
fs.mkdirSync(objLib);
|
||||
} catch(err) {
|
||||
}
|
||||
fs.mkdirSync(path.join(objLib,"A"));
|
||||
fs.mkdirSync(path.join(objLib,"B"));
|
||||
fs.mkdirSync(path.join(objLib,"B","C"));
|
||||
if (type === "functions" || type === "object") {
|
||||
fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8');
|
||||
fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8');
|
||||
}
|
||||
if (type === "flows" || type === "object") {
|
||||
fs.writeFileSync(path.join(objLib,"B","flow.json"),"Hi",'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
it('should return a directory listing of library objects',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
createObjectLibrary();
|
||||
|
||||
localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) {
|
||||
flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]);
|
||||
localfilesystemLibrary.getLibraryEntry('object','B').then(function(flows) {
|
||||
flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' } ]);
|
||||
localfilesystemLibrary.getLibraryEntry('object','B/C').then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load a flow library object with .json unspecified', function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
createObjectLibrary("flows");
|
||||
localfilesystemLibrary.getLibraryEntry('flows','B/flow').then(function(flows) {
|
||||
flows.should.eql("Hi");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should return a library object',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
createObjectLibrary();
|
||||
localfilesystemLibrary.getLibraryEntry('object','B/file2.js').then(function(body) {
|
||||
body.should.eql("// not a metaline \n\n Hi");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a newly saved library function',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
createObjectLibrary("functions");
|
||||
localfilesystemLibrary.getLibraryEntry('functions','B').then(function(flows) {
|
||||
flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' } ]);
|
||||
var ft = path.join("B","D","file3.js");
|
||||
localfilesystemLibrary.saveLibraryEntry('functions',ft,{mno:'pqr'},"// another non meta line\n\n Hi There").then(function() {
|
||||
setTimeout(function() {
|
||||
localfilesystemLibrary.getLibraryEntry('functions',path.join("B","D")).then(function(flows) {
|
||||
flows.should.eql([ { mno: 'pqr', fn: 'file3.js' } ]);
|
||||
localfilesystemLibrary.getLibraryEntry('functions',ft).then(function(body) {
|
||||
body.should.eql("// another non meta line\n\n Hi There");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
})
|
||||
}, 50);
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a newly saved library flow',function(done) {
|
||||
localfilesystemLibrary.init({userDir:userDir}).then(function() {
|
||||
createObjectLibrary("flows");
|
||||
localfilesystemLibrary.getLibraryEntry('flows','B').then(function(flows) {
|
||||
flows.should.eql([ 'C', {fn:'flow.json'} ]);
|
||||
var ft = path.join("B","D","file3");
|
||||
localfilesystemLibrary.saveLibraryEntry('flows',ft,{mno:'pqr'},"Hi").then(function() {
|
||||
setTimeout(function() {
|
||||
localfilesystemLibrary.getLibraryEntry('flows',path.join("B","D")).then(function(flows) {
|
||||
flows.should.eql([ { mno: 'pqr', fn: 'file3.json' } ]);
|
||||
localfilesystemLibrary.getLibraryEntry('flows',ft+".json").then(function(body) {
|
||||
body.should.eql("Hi");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
})
|
||||
}, 50);
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
describe("storage/localfilesystem/projects/Project", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
})
|
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 defaultFileSet = require("../../../../../../red/runtime/storage/localfilesystem/projects/defaultFileSet");
|
||||
|
||||
describe('storage/localfilesystem/projects/defaultFileSet', function() {
|
||||
var runtime = {
|
||||
i18n: {
|
||||
"_": function(name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
it('generates package.json for a project', function() {
|
||||
var generated = defaultFileSet["package.json"]({
|
||||
name: "A TEST NAME",
|
||||
summary: "A TEST SUMMARY",
|
||||
files: {
|
||||
flow: "MY FLOW FILE",
|
||||
credentials: "MY CREDENTIALS FILE"
|
||||
}
|
||||
}, runtime);
|
||||
|
||||
var parsed = JSON.parse(generated);
|
||||
parsed.should.have.property('name',"A TEST NAME");
|
||||
parsed.should.have.property('description',"A TEST SUMMARY");
|
||||
parsed.should.have.property('node-red');
|
||||
parsed['node-red'].should.have.property('settings');
|
||||
parsed['node-red'].settings.should.have.property('flowFile',"MY FLOW FILE");
|
||||
parsed['node-red'].settings.should.have.property('credentialsFile',"MY CREDENTIALS FILE");
|
||||
});
|
||||
|
||||
it('generates README.md for a project', function() {
|
||||
var generated = defaultFileSet["README.md"]({
|
||||
name: "A TEST NAME",
|
||||
summary: "A TEST SUMMARY"
|
||||
}, runtime);
|
||||
generated.should.match(/A TEST NAME/);
|
||||
generated.should.match(/A TEST SUMMARY/);
|
||||
});
|
||||
it('generates .gitignore for a project', function() {
|
||||
var generated = defaultFileSet[".gitignore"]({
|
||||
name: "A TEST NAME",
|
||||
summary: "A TEST SUMMARY"
|
||||
}, runtime);
|
||||
generated.length.should.be.greaterThan(0);
|
||||
});
|
||||
});
|
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 authCache = require("../../../../../../../red/runtime/storage/localfilesystem/projects/git/authCache")
|
||||
|
||||
describe("localfilesystem/projects/git/authCache", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
authCache.init();
|
||||
});
|
||||
afterEach(function() {
|
||||
authCache.init();
|
||||
});
|
||||
|
||||
it('sets/clears auth details for a given project/remote/user', function() {
|
||||
should.not.exist(authCache.get("project","remote1","user1"));
|
||||
should.not.exist(authCache.get("project","remote1","user2"));
|
||||
|
||||
authCache.set("project","remote1","user1",{foo1:"bar1"});
|
||||
authCache.set("project","remote1","user2",{foo2:"bar2"});
|
||||
|
||||
var result = authCache.get("project","remote1","user1");
|
||||
result.should.have.property("foo1","bar1");
|
||||
|
||||
result = authCache.get("project","remote1","user2");
|
||||
result.should.have.property("foo2","bar2");
|
||||
|
||||
authCache.clear("project","remote1","user1");
|
||||
should.not.exist(authCache.get("project","remote1","user1"));
|
||||
should.exist(authCache.get("project","remote1","user2"));
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('clears auth details for all users on a given project/remote', function() {
|
||||
|
||||
authCache.set("project","remote1","user1",{foo1:"bar1"});
|
||||
authCache.set("project","remote1","user2",{foo2:"bar2"});
|
||||
authCache.set("project","remote2","user1",{foo3:"bar3"});
|
||||
|
||||
should.exist(authCache.get("project","remote1","user1"));
|
||||
should.exist(authCache.get("project","remote1","user2"));
|
||||
should.exist(authCache.get("project","remote2","user1"));
|
||||
|
||||
authCache.clear("project","remote1");
|
||||
should.not.exist(authCache.get("project","remote1","user1"));
|
||||
should.not.exist(authCache.get("project","remote1","user2"));
|
||||
should.exist(authCache.get("project","remote2","user1"));
|
||||
});
|
||||
|
||||
it('clears auth details for all remotes/users on a given project', function() {
|
||||
|
||||
authCache.set("project1","remote1","user1",{foo1:"bar1"});
|
||||
authCache.set("project1","remote1","user2",{foo2:"bar2"});
|
||||
authCache.set("project2","remote2","user1",{foo3:"bar3"});
|
||||
|
||||
should.exist(authCache.get("project1","remote1","user1"));
|
||||
should.exist(authCache.get("project1","remote1","user2"));
|
||||
should.exist(authCache.get("project2","remote2","user1"));
|
||||
|
||||
authCache.clear("project2");
|
||||
should.exist(authCache.get("project1","remote1","user1"));
|
||||
should.exist(authCache.get("project1","remote1","user2"));
|
||||
should.not.exist(authCache.get("project2","remote2","user1"));
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var child_process = require("child_process");
|
||||
var fs = require("fs-extra");
|
||||
|
||||
var authServer = require("../../../../../../../red/runtime/storage/localfilesystem/projects/git/authServer");
|
||||
|
||||
|
||||
var sendPrompt = function(localPath, prompt) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var response;
|
||||
var socket = net.connect(localPath, function() {
|
||||
socket.on('data', function(data) { response = data; socket.end() });
|
||||
socket.on('end', function() {
|
||||
resolve(response);
|
||||
});
|
||||
socket.on('error',reject);
|
||||
socket.write(prompt+"\n", 'utf8');
|
||||
});
|
||||
socket.setEncoding('utf8');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
describe("localfilesystem/projects/git/authServer", function() {
|
||||
it("listens for user/pass prompts and returns provided auth", function(done) {
|
||||
authServer.ResponseServer({username: "TEST_USER", password: "TEST_PASS"}).then(function(rs) {
|
||||
sendPrompt(rs.path,"Username").then(function(response) {
|
||||
response.should.eql("TEST_USER");
|
||||
return sendPrompt(rs.path,"Password");
|
||||
}).then(function(response) {
|
||||
response.should.eql("TEST_PASS");
|
||||
}).then(() => {
|
||||
rs.close();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
rs.close();
|
||||
done(err);
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
it("listens for ssh prompts and returns provided auth", function(done) {
|
||||
authServer.ResponseSSHServer({passphrase: "TEST_PASSPHRASE"}).then(function(rs) {
|
||||
sendPrompt(rs.path,"The").then(function(response) {
|
||||
// TODO:
|
||||
response.should.eql("yes");
|
||||
return sendPrompt(rs.path,"Enter");
|
||||
}).then(function(response) {
|
||||
response.should.eql("TEST_PASSPHRASE");
|
||||
}).then(() => {
|
||||
rs.close();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
rs.close();
|
||||
done(err);
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
});
|
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var child_process = require("child_process");
|
||||
var fs = require("fs-extra");
|
||||
|
||||
var authWriter = "../../../../../../../red/runtime/storage/localfilesystem/projects/git/authWriter";
|
||||
|
||||
function getListenPath() {
|
||||
var seed = (0x100000+Math.random()*0x999999).toString(16);
|
||||
var fn = 'node-red-git-askpass-'+seed+'-sock';
|
||||
var listenPath;
|
||||
if (process.platform === 'win32') {
|
||||
listenPath = '\\\\.\\pipe\\'+fn;
|
||||
} else {
|
||||
listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn);
|
||||
}
|
||||
// console.log(listenPath);
|
||||
return listenPath;
|
||||
}
|
||||
|
||||
|
||||
describe("localfilesystem/projects/git/authWriter", function() {
|
||||
it("connects to port and sends passphrase", function(done) {
|
||||
var receivedData = "";
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.setEncoding('utf8');
|
||||
connection.on('data', function(data) {
|
||||
receivedData += data;
|
||||
var m = data.indexOf("\n");
|
||||
if (m !== -1) {
|
||||
connection.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var listenPath = getListenPath();
|
||||
|
||||
server.listen(listenPath, function(ready) {
|
||||
child_process.exec('"'+process.execPath+'" "'+authWriter+'" "'+listenPath+'" TEST_PHRASE_FOO',{cwd:__dirname}, (error,stdout,stderr) => {
|
||||
server.close();
|
||||
try {
|
||||
should.not.exist(error);
|
||||
receivedData.should.eql("TEST_PHRASE_FOO\n");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
server.on('close', function() {
|
||||
// console.log("Closing response server");
|
||||
fs.removeSync(listenPath);
|
||||
});
|
||||
server.on('error',function(err) {
|
||||
console.log("ResponseServer unexpectedError:",err.toString());
|
||||
server.close();
|
||||
done(err);
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
});
|
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
describe("storage/localfilesystem/projects/git/index", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
})
|
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
describe("storage/localfilesystem/projects/index", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
})
|
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* 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 sshkeys = require("../../../../../../../red/runtime/storage/localfilesystem/projects/ssh");
|
||||
|
||||
describe("storage/localfilesystem/projects/ssh", function() {
|
||||
var userDir = path.join(__dirname,".testSSHKeyUserHome");
|
||||
var mockSettings = {
|
||||
userDir: userDir
|
||||
};
|
||||
var mockRuntime = {
|
||||
log:{
|
||||
_:function() { return "placeholder message"},
|
||||
info: function() { },
|
||||
log: function() { },
|
||||
trace: function() { }
|
||||
}
|
||||
};
|
||||
var oldHOME;
|
||||
|
||||
beforeEach(function(done) {
|
||||
oldHOME = process.env.HOME;
|
||||
process.env.HOME = "/tmp/doesnt/exist";
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,done);
|
||||
});
|
||||
});
|
||||
afterEach(function(done) {
|
||||
process.env.HOME = oldHOME;
|
||||
fs.remove(userDir,done);
|
||||
});
|
||||
|
||||
it('should create sshkey directory when sshkey initializes', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
var ret = fs.existsSync(sshkeyDirPath);
|
||||
ret.should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get sshkey empty list if there is no sshkey file', function(done) {
|
||||
var username = 'test';
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(0);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get sshkey list', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var filenameList = ['test-key01', 'test-key02'];
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
for(var filename of filenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
|
||||
for(var filename of filenameList) {
|
||||
retObj.should.containEql({ name: filename });
|
||||
}
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get sshkey file if there is only private key', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var filenameList = ['test-key01', 'test-key02'];
|
||||
var onlyPrivateKeyFilenameList = ['test-key03', 'test-key04'];
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
for(var filename of filenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
for(var filename of onlyPrivateKeyFilenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
}
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
|
||||
for(var filename of filenameList) {
|
||||
retObj.should.containEql({ name: filename });
|
||||
}
|
||||
for(var filename of onlyPrivateKeyFilenameList) {
|
||||
retObj.should.not.containEql({ name: filename });
|
||||
}
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get sshkey file if there is only public key', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var filenameList = ['test-key01', 'test-key02'];
|
||||
var directoryList = ['test-key03', '.test-key04'];
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
for(var filename of filenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
for(var filename of directoryList) {
|
||||
fs.ensureDirSync(path.join(sshkeyDirPath,filename));
|
||||
}
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
|
||||
for(var filename of filenameList) {
|
||||
retObj.should.containEql({ name: filename });
|
||||
}
|
||||
for(var directoryname of directoryList) {
|
||||
retObj.should.not.containEql({ name: directoryname });
|
||||
}
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get sshkey list that does not have directory', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var otherUsername = 'other';
|
||||
var filenameList = ['test-key01', 'test-key02'];
|
||||
var otherUserFilenameList = ['test-key03', 'test-key04'];
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
for(var filename of filenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
for(var filename of otherUserFilenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
|
||||
for(var filename of filenameList) {
|
||||
retObj.should.containEql({ name: filename });
|
||||
}
|
||||
for(var filename of otherUserFilenameList) {
|
||||
retObj.should.not.containEql({ name: filename });
|
||||
}
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get sshkey list that have keys of specified user', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var otherUsername = 'other';
|
||||
var filenameList = ['test-key01', 'test-key02'];
|
||||
var otherUserFilenameList = ['test-key03', 'test-key04'];
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
for(var filename of filenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
for(var filename of otherUserFilenameList) {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename+".pub"),"","utf8");
|
||||
}
|
||||
sshkeys.listSSHKeys(username).then(function(retObj) {
|
||||
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
|
||||
for(var filename of filenameList) {
|
||||
retObj.should.containEql({ name: filename });
|
||||
}
|
||||
for(var filename of otherUserFilenameList) {
|
||||
retObj.should.not.containEql({ name: filename });
|
||||
}
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate sshkey file with empty data', function(done) {
|
||||
this.timeout(10000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
name: 'test-key01'
|
||||
};
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
retObj.should.be.equal(options.name);
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate sshkey file with only comment data', function(done) {
|
||||
this.timeout(10000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01'
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
retObj.should.be.equal(options.name);
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate sshkey file with password data', function(done) {
|
||||
this.timeout(10000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01',
|
||||
password: 'testtest'
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
retObj.should.be.equal(options.name);
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate sshkey file with size data', function(done) {
|
||||
this.timeout(20000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01',
|
||||
size: 4096
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
retObj.should.be.equal(options.name);
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate sshkey file with password & size data', function(done) {
|
||||
this.timeout(20000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01',
|
||||
password: 'testtest',
|
||||
size: 4096
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
retObj.should.be.equal(options.name);
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not generate sshkey file with illegal size data', function(done) {
|
||||
this.timeout(10000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01',
|
||||
size: 1023
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
done(new Error('Does NOT throw error!'));
|
||||
}).catch(function(err) {
|
||||
try {
|
||||
err.should.have.property('code', 'key_length_too_short');
|
||||
done();
|
||||
}
|
||||
catch (error) {
|
||||
done(error);
|
||||
}
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not generate sshkey file with illegal password', function(done) {
|
||||
this.timeout(10000);
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var options = {
|
||||
comment: 'test@test.com',
|
||||
name: 'test-key01',
|
||||
password: 'aa'
|
||||
};
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
sshkeys.generateSSHKey(username, options).then(function(retObj) {
|
||||
done(new Error('Does NOT throw error!'));
|
||||
}).catch(function(err) {
|
||||
try {
|
||||
err.should.have.property('code', 'key_passphrase_too_short');
|
||||
done();
|
||||
}
|
||||
catch (error) {
|
||||
done(error);
|
||||
}
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get sshkey file content', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var filename = 'test-key01';
|
||||
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";
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),fileContent,"utf8");
|
||||
sshkeys.getSSHKey(username, filename).then(function(retObj) {
|
||||
retObj.should.be.equal(fileContent);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete sshkey files', function(done) {
|
||||
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
|
||||
var username = 'test';
|
||||
var filename = 'test-key01';
|
||||
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";
|
||||
|
||||
sshkeys.init(mockSettings, mockRuntime).then(function() {
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
|
||||
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),fileContent,"utf8");
|
||||
sshkeys.deleteSSHKey(username, filename).then(function() {
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.false();
|
||||
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.false();
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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 child_process = require('child_process');
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var keygen = require("../../../../../../../red/runtime/storage/localfilesystem/projects/ssh/keygen")
|
||||
|
||||
describe("localfilesystem/projects/ssh/keygen", function() {
|
||||
|
||||
afterEach(function() {
|
||||
if (child_process.spawn.restore) {
|
||||
child_process.spawn.restore();
|
||||
}
|
||||
})
|
||||
|
||||
it("invokes sshkeygen", function(done) {
|
||||
var command;
|
||||
var args;
|
||||
var opts;
|
||||
sinon.stub(child_process,"spawn", function(_command,_args,_opts) {
|
||||
_command = command;
|
||||
_args = args;
|
||||
_opts = opts;
|
||||
|
||||
var e = new EventEmitter();
|
||||
e.stdout = new EventEmitter();
|
||||
e.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
e.stdout.emit("data","result");
|
||||
e.emit("close",0);
|
||||
},50)
|
||||
return e;
|
||||
});
|
||||
|
||||
keygen.generateKey({
|
||||
size: 1024,
|
||||
location: 'location',
|
||||
comment: 'comment',
|
||||
password: 'password'
|
||||
}).then(function(output) {
|
||||
output.should.equal("result");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
})
|
||||
})
|
||||
|
||||
it("reports passphrase too short", function(done) {
|
||||
var command;
|
||||
var args;
|
||||
var opts;
|
||||
|
||||
try {
|
||||
keygen.generateKey({
|
||||
size: 1024,
|
||||
location: 'location',
|
||||
comment: 'comment',
|
||||
password: '123'
|
||||
}).then(function(output) {
|
||||
done(new Error("Error not thrown"));
|
||||
}).catch(function(err) {
|
||||
done(new Error("Error not thrown"));
|
||||
})
|
||||
} catch(err) {
|
||||
err.should.have.property("code","key_passphrase_too_short");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it("reports key length too short", function(done) {
|
||||
var command;
|
||||
var args;
|
||||
var opts;
|
||||
try {
|
||||
keygen.generateKey({
|
||||
size: 123,
|
||||
location: 'location',
|
||||
comment: 'comment',
|
||||
password: 'password'
|
||||
}).then(function(output) {
|
||||
done(new Error("Error not thrown"));
|
||||
}).catch(function(err) {
|
||||
done(new Error("Error not thrown"));
|
||||
})
|
||||
} catch(err) {
|
||||
err.should.have.property("code","key_length_too_short");
|
||||
done();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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 localfilesystemSessions = require("../../../../../red/runtime/storage/localfilesystem/sessions");
|
||||
|
||||
describe('storage/localfilesystem/sessions', function() {
|
||||
var userDir = path.join(__dirname,".testUserHome");
|
||||
beforeEach(function(done) {
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,done);
|
||||
});
|
||||
});
|
||||
afterEach(function(done) {
|
||||
fs.remove(userDir,done);
|
||||
});
|
||||
it('should handle non-existent sessions', function(done) {
|
||||
var sessionsFile = path.join(userDir,".sessions.json");
|
||||
|
||||
localfilesystemSessions.init({userDir:userDir});
|
||||
fs.existsSync(sessionsFile).should.be.false();
|
||||
localfilesystemSessions.getSessions().then(function(sessions) {
|
||||
sessions.should.eql({});
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle corrupt sessions', function(done) {
|
||||
var sessionsFile = path.join(userDir,".sessions.json");
|
||||
fs.writeFileSync(sessionsFile,"[This is not json","utf8");
|
||||
localfilesystemSessions.init({userDir:userDir});
|
||||
fs.existsSync(sessionsFile).should.be.true();
|
||||
localfilesystemSessions.getSessions().then(function(sessions) {
|
||||
sessions.should.eql({});
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle sessions', function(done) {
|
||||
var sessionsFile = path.join(userDir,".sessions.json");
|
||||
|
||||
localfilesystemSessions.init({userDir:userDir});
|
||||
fs.existsSync(sessionsFile).should.be.false();
|
||||
|
||||
var sessions = {"abc":{"type":"creds"}};
|
||||
|
||||
localfilesystemSessions.saveSessions(sessions).then(function() {
|
||||
fs.existsSync(sessionsFile).should.be.true();
|
||||
localfilesystemSessions.getSessions().then(function(_sessions) {
|
||||
_sessions.should.eql(sessions);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 localfilesystemSettings = require("../../../../../red/runtime/storage/localfilesystem/settings");
|
||||
|
||||
describe('storage/localfilesystem/settings', function() {
|
||||
var userDir = path.join(__dirname,".testUserHome");
|
||||
beforeEach(function(done) {
|
||||
fs.remove(userDir,function(err) {
|
||||
fs.mkdir(userDir,done);
|
||||
});
|
||||
});
|
||||
afterEach(function(done) {
|
||||
fs.remove(userDir,done);
|
||||
});
|
||||
|
||||
it('should handle non-existent settings', function(done) {
|
||||
var settingsFile = path.join(userDir,".settings.json");
|
||||
|
||||
localfilesystemSettings.init({userDir:userDir});
|
||||
fs.existsSync(settingsFile).should.be.false();
|
||||
localfilesystemSettings.getSettings().then(function(settings) {
|
||||
settings.should.eql({});
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle corrupt settings', function(done) {
|
||||
var settingsFile = path.join(userDir,".config.json");
|
||||
fs.writeFileSync(settingsFile,"[This is not json","utf8");
|
||||
localfilesystemSettings.init({userDir:userDir});
|
||||
fs.existsSync(settingsFile).should.be.true();
|
||||
localfilesystemSettings.getSettings().then(function(settings) {
|
||||
settings.should.eql({});
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle settings', function(done) {
|
||||
var settingsFile = path.join(userDir,".config.json");
|
||||
|
||||
localfilesystemSettings.init({userDir:userDir});
|
||||
fs.existsSync(settingsFile).should.be.false();
|
||||
|
||||
var settings = {"abc":{"type":"creds"}};
|
||||
|
||||
localfilesystemSettings.saveSettings(settings).then(function() {
|
||||
fs.existsSync(settingsFile).should.be.true();
|
||||
localfilesystemSettings.getSettings().then(function(_settings) {
|
||||
_settings.should.eql(settings);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 util = require("../../../../../red/runtime/storage/localfilesystem/util");
|
||||
|
||||
describe('storage/localfilesystem/util', function() {
|
||||
describe('parseJSON', function() {
|
||||
it('returns parsed JSON', function() {
|
||||
var result = util.parseJSON('{"a":123}');
|
||||
result.should.eql({a:123});
|
||||
})
|
||||
it('ignores BOM character', function() {
|
||||
var result = util.parseJSON('\uFEFF{"a":123}');
|
||||
result.should.eql({a:123});
|
||||
})
|
||||
})
|
||||
});
|
Reference in New Issue
Block a user