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

Evaluate all env vars as part of async flow start

This commit is contained in:
Nick O'Leary 2023-06-23 02:11:57 +01:00
parent 8db2972288
commit 1c5fdb6ab6
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
16 changed files with 1141 additions and 1290 deletions

View File

@ -112,7 +112,7 @@
"mermaid": "^9.4.3", "mermaid": "^9.4.3",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.2.2", "mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.1", "node-red-node-test-helper": "^0.3.2",
"nodemon": "2.0.20", "nodemon": "2.0.20",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.62.1", "sass": "1.62.1",

View File

@ -14,19 +14,20 @@
* limitations under the License. * limitations under the License.
**/ **/
var clone = require("clone"); const clone = require("clone");
var redUtil = require("@node-red/util").util; const redUtil = require("@node-red/util").util;
const events = require("@node-red/util").events; const events = require("@node-red/util").events;
var flowUtil = require("./util"); const flowUtil = require("./util");
const context = require('../nodes/context'); const context = require('../nodes/context');
const hooks = require("@node-red/util").hooks; const hooks = require("@node-red/util").hooks;
const credentials = require("../nodes/credentials"); const credentials = require("../nodes/credentials");
var Subflow; let Subflow;
var Log; let Log;
let Group;
var nodeCloseTimeout = 15000; let nodeCloseTimeout = 15000;
var asyncMessageDelivery = true; let asyncMessageDelivery = true;
/** /**
* This class represents a flow within the runtime. It is responsible for * This class represents a flow within the runtime. It is responsible for
@ -52,6 +53,27 @@ class Flow {
this.isGlobalFlow = false; this.isGlobalFlow = false;
} }
this.id = this.flow.id || "global"; this.id = this.flow.id || "global";
// Initialise the group objects. These must be done in the right order
// starting from outer-most to inner-most so that the parent hierarchy
// is maintained.
this.groups = {}
this.groupOrder = []
const groupIds = Object.keys(this.flow.groups || {})
while (groupIds.length > 0) {
const id = groupIds.shift()
const groupDef = this.flow.groups[id]
if (!groupDef.g || this.groups[groupDef.g]) {
// The parent of this group is available - either another group
// or the top-level flow (this)
const parent = this.groups[groupDef.g] || this
this.groups[groupDef.id] = new Group(parent, groupDef)
this.groupOrder.push(groupDef.id)
} else {
// Try again once we've processed the other groups
groupIds.push(id)
}
}
this.activeNodes = {}; this.activeNodes = {};
this.subflowInstanceNodes = {}; this.subflowInstanceNodes = {};
this.catchNodes = []; this.catchNodes = [];
@ -59,6 +81,11 @@ class Flow {
this.path = this.id; this.path = this.id;
// Ensure a context exists for this flow // Ensure a context exists for this flow
this.context = context.getFlowContext(this.id,this.parent.id); this.context = context.getFlowContext(this.id,this.parent.id);
// env is an array of env definitions
// _env is an object for direct lookup of env name -> value
this.env = this.flow.env
this._env = {}
} }
/** /**
@ -136,7 +163,7 @@ class Flow {
* @param {[type]} msg [description] * @param {[type]} msg [description]
* @return {[type]} [description] * @return {[type]} [description]
*/ */
start(diff) { async start(diff) {
this.trace("start "+this.TYPE+" ["+this.path+"]"); this.trace("start "+this.TYPE+" ["+this.path+"]");
var node; var node;
var newNode; var newNode;
@ -145,6 +172,18 @@ class Flow {
this.statusNodes = []; this.statusNodes = [];
this.completeNodeMap = {}; this.completeNodeMap = {};
if (this.env) {
this._env = await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id))
// console.log('env', this.env)
// console.log('_env', this._env)
}
for (let i = 0; i < this.groupOrder.length; i++) {
// Start the groups in the right order so they
// can setup their env vars knowning their parent
// will have been started
await this.groups[this.groupOrder[i]].start()
}
var configNodes = Object.keys(this.flow.configs); var configNodes = Object.keys(this.flow.configs);
var configNodeAttempts = {}; var configNodeAttempts = {};
while (configNodes.length > 0) { while (configNodes.length > 0) {
@ -177,7 +216,7 @@ class Flow {
} }
} }
if (readyToCreate) { if (readyToCreate) {
newNode = flowUtil.createNode(this,node); newNode = await flowUtil.createNode(this,node);
if (newNode) { if (newNode) {
this.activeNodes[id] = newNode; this.activeNodes[id] = newNode;
} }
@ -203,7 +242,7 @@ class Flow {
if (node.d !== true) { if (node.d !== true) {
if (!node.subflow) { if (!node.subflow) {
if (!this.activeNodes[id]) { if (!this.activeNodes[id]) {
newNode = flowUtil.createNode(this,node); newNode = await flowUtil.createNode(this,node);
if (newNode) { if (newNode) {
this.activeNodes[id] = newNode; this.activeNodes[id] = newNode;
} }
@ -221,7 +260,7 @@ class Flow {
node node
); );
this.subflowInstanceNodes[id] = subflow; this.subflowInstanceNodes[id] = subflow;
subflow.start(); await subflow.start();
this.activeNodes[id] = subflow.node; this.activeNodes[id] = subflow.node;
// this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id}); // this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
@ -404,8 +443,7 @@ class Flow {
* @return {Node} group node * @return {Node} group node
*/ */
getGroupNode(id) { getGroupNode(id) {
const groups = this.global.groups; return this.groups[id];
return groups[id];
} }
/** /**
@ -416,95 +454,8 @@ class Flow {
return this.activeNodes; return this.activeNodes;
} }
/*!
* Get value of environment variable defined in group node.
* @param {String} group - group node
* @param {String} name - name of variable
* @return {Object} object containing the value in val property or null if not defined
*/
getGroupEnvSetting(node, group, name) {
if (group) {
if (name === "NR_GROUP_NAME") {
return [{
val: group.name
}, null];
}
if (name === "NR_GROUP_ID") {
return [{
val: group.id
}, null];
}
if (group.credentials === undefined) {
group.credentials = credentials.get(group.id) || {};
}
if (!name.startsWith("$parent.")) {
if (group.env) {
if (!group._env) {
const envs = group.env;
const entries = envs.map((env) => {
if (env.type === "cred") {
const cred = group.credentials;
if (cred.hasOwnProperty(env.name)) {
env.value = cred[env.name];
}
}
return [env.name, env];
});
group._env = Object.fromEntries(entries);
}
const env = group._env[name];
if (env) {
let value = env.value;
const type = env.type;
if ((type !== "env") ||
(value !== name)) {
if (type === "env") {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
}
if (type === "bool") {
const val
= ((value === "true") ||
(value === true));
return [{
val: val
}, null];
}
if (type === "cred") {
return [{
val: value
}, null];
}
try {
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
return [{
val: val
}, null];
}
catch (e) {
this.error(e);
return [null, null];
}
}
}
}
}
else {
name = name.substring(8);
}
if (group.g) {
const parent = this.getGroupNode(group.g);
return this.getGroupEnvSetting(node, parent, name);
}
}
return [null, name];
}
/** /**
* Get a flow setting value. This currently automatically defers to the parent * Get a flow setting value.
* flow which, as defined in ./index.js returns `process.env[key]`.
* This lays the groundwork for Subflow to have instance-specific settings
* @param {[type]} key [description] * @param {[type]} key [description]
* @return {[type]} [description] * @return {[type]} [description]
*/ */
@ -516,54 +467,14 @@ class Flow {
if (key === "NR_FLOW_ID") { if (key === "NR_FLOW_ID") {
return flow.id; return flow.id;
} }
if (flow.credentials === undefined) { if (!key.startsWith("$parent.")) {
flow.credentials = credentials.get(flow.id) || {}; if (this._env.hasOwnProperty(key)) {
} return this._env[key]
if (flow.env) {
if (!key.startsWith("$parent.")) {
if (!flow._env) {
const envs = flow.env;
const entries = envs.map((env) => {
if (env.type === "cred") {
const cred = flow.credentials;
if (cred.hasOwnProperty(env.name)) {
env.value = cred[env.name];
}
}
return [env.name, env]
});
flow._env = Object.fromEntries(entries);
}
const env = flow._env[key];
if (env) {
let value = env.value;
const type = env.type;
if ((type !== "env") || (value !== key)) {
if (type === "env") {
value = value.replace(new RegExp("\\${"+key+"}","g"),"${$parent."+key+"}");
}
try {
if (type === "bool") {
const val = ((value === "true") ||
(value === true));
return val;
}
if (type === "cred") {
return value;
}
var val = redUtil.evaluateNodeProperty(value, type, null, null, null);
return val;
}
catch (e) {
this.error(e);
}
}
}
} }
else { } else {
key = key.substring(8); key = key.substring(8);
}
} }
// Delegate to the parent flow.
return this.parent.getSetting(key); return this.parent.getSetting(key);
} }
@ -618,10 +529,10 @@ class Flow {
let distance = 0 let distance = 0
if (reportingNode.g) { if (reportingNode.g) {
// Reporting node inside a group. Calculate the distance between it and the status node // Reporting node inside a group. Calculate the distance between it and the status node
let containingGroup = this.global.groups[reportingNode.g] let containingGroup = this.groups[reportingNode.g]
while (containingGroup && containingGroup.id !== targetStatusNode.g) { while (containingGroup && containingGroup.id !== targetStatusNode.g) {
distance++ distance++
containingGroup = this.global.groups[containingGroup.g] containingGroup = this.groups[containingGroup.g]
} }
if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') { if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') {
// This status node is in a group, but not in the same hierachy // This status node is in a group, but not in the same hierachy
@ -706,10 +617,10 @@ class Flow {
let distance = 0 let distance = 0
if (reportingNode.g) { if (reportingNode.g) {
// Reporting node inside a group. Calculate the distance between it and the catch node // Reporting node inside a group. Calculate the distance between it and the catch node
let containingGroup = this.global.groups[reportingNode.g] let containingGroup = this.groups[reportingNode.g]
while (containingGroup && containingGroup.id !== targetCatchNode.g) { while (containingGroup && containingGroup.id !== targetCatchNode.g) {
distance++ distance++
containingGroup = this.global.groups[containingGroup.g] containingGroup = this.groups[containingGroup.g]
} }
if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') { if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') {
// This catch node is in a group, but not in the same hierachy // This catch node is in a group, but not in the same hierachy
@ -909,9 +820,10 @@ module.exports = {
asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery
Log = runtime.log; Log = runtime.log;
Subflow = require("./Subflow"); Subflow = require("./Subflow");
Group = require("./Group").Group
}, },
create: function(parent,global,conf) { create: function(parent,global,conf) {
return new Flow(parent,global,conf); return new Flow(parent,global,conf)
}, },
Flow: Flow Flow: Flow
} }

View File

@ -0,0 +1,55 @@
const flowUtil = require("./util");
const credentials = require("../nodes/credentials");
/**
* This class represents a group within the runtime.
*/
class Group {
/**
* Create a Group object.
* @param {[type]} parent The parent flow/group
* @param {[type]} groupDef This group's definition
*/
constructor(parent, groupDef) {
this.TYPE = 'group'
this.name = groupDef.name
this.parent = parent
this.group = groupDef
this.id = this.group.id
this.g = this.group.g
this.env = this.group.env
this._env = {}
}
async start() {
if (this.env) {
this._env = await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id))
}
}
/**
* Get a group setting value.
* @param {[type]} key [description]
* @return {[type]} [description]
*/
getSetting(key) {
if (key === "NR_GROUP_NAME") {
return this.name;
}
if (key === "NR_GROUP_ID") {
return this.id;
}
if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) {
return this._env[key]
}
} else {
key = key.substring(8);
}
return this.parent.getSetting(key);
}
}
module.exports = {
Group
}

View File

@ -119,7 +119,7 @@ class Subflow extends Flow {
this.templateCredentials = credentials.get(subflowDef.id) || {}; this.templateCredentials = credentials.get(subflowDef.id) || {};
this.instanceCredentials = credentials.get(id) || {}; this.instanceCredentials = credentials.get(id) || {};
var env = []; var env = {};
if (this.subflowDef.env) { if (this.subflowDef.env) {
this.subflowDef.env.forEach(e => { this.subflowDef.env.forEach(e => {
env[e.name] = e; env[e.name] = e;
@ -145,7 +145,7 @@ class Subflow extends Flow {
} }
}); });
} }
this.env = env; this.env = Object.values(env);
} }
/** /**
@ -156,7 +156,7 @@ class Subflow extends Flow {
* @param {[type]} diff [description] * @param {[type]} diff [description]
* @return {[type]} [description] * @return {[type]} [description]
*/ */
start(diff) { async start(diff) {
var self = this; var self = this;
// Create a subflow node to accept inbound messages and route appropriately // Create a subflow node to accept inbound messages and route appropriately
var Node = require("../nodes/Node"); var Node = require("../nodes/Node");
@ -310,7 +310,7 @@ class Subflow extends Flow {
} }
} }
} }
super.start(diff); return super.start(diff);
} }
/** /**
@ -335,68 +335,35 @@ class Subflow extends Flow {
} }
/** /**
* Get environment variable of subflow * Get environment variable of subflow
* @param {String} name name of env var * @param {String} key name of env var
* @return {Object} val value of env var * @return {Object} val value of env var
*/ */
getSetting(name) { getSetting(key) {
if (!/^\$parent\./.test(name)) {
var env = this.env;
if (env && env.hasOwnProperty(name)) {
var val = env[name];
// If this is an env type property we need to be careful not
// to get into lookup loops.
// 1. if the value to lookup is the same as this one, go straight to parent
// 2. otherwise, check if it is a compound env var ("foo $(bar)")
// and if so, substitute any instances of `name` with $parent.name
// See https://github.com/node-red/node-red/issues/2099
if (val.type !== 'env' || val.value !== name) {
let value = val.value;
var type = val.type;
if (type === 'env') {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
}
try {
return evaluateInputValue(value, type, this.node);
}
catch (e) {
this.error(e);
return undefined;
}
} else {
// This _is_ an env property pointing at itself - go to parent
}
}
} else {
// name starts $parent. ... so delegate to parent automatically
name = name.substring(8);
}
const node = this.subflowInstance; const node = this.subflowInstance;
if (node) { if (node) {
if (name === "NR_NODE_NAME") { if (key === "NR_NODE_NAME") {
return node.name; return node.name;
} }
if (name === "NR_NODE_ID") { if (key === "NR_NODE_ID") {
return node.id; return node.id;
} }
if (name === "NR_NODE_PATH") { if (key === "NR_NODE_PATH") {
return node._path; return node._path;
} }
} }
if (node.g) { if (!key.startsWith("$parent.")) {
const group = this.getGroupNode(node.g); if (this._env.hasOwnProperty(key)) {
const [result, newName] = this.getGroupEnvSetting(node, group, name); return this._env[key]
if (result) {
return result.val;
} }
name = newName; } else {
key = key.substring(8);
} }
// Push the request up to the parent.
var parent = this.parent; // Unlike a Flow, the parent of a Subflow could be a Group
if (parent) { if (node.g) {
var val = parent.getSetting(name); return this.parent.getGroupNode(node.g).getSetting(key)
return val;
} }
return undefined; return this.parent.getSetting(key)
} }
/** /**

View File

@ -391,7 +391,7 @@ async function start(type,diff,muteLog,isDeploy) {
for (id in activeFlows) { for (id in activeFlows) {
if (activeFlows.hasOwnProperty(id)) { if (activeFlows.hasOwnProperty(id)) {
try { try {
activeFlows[id].start(diff); await activeFlows[id].start(diff);
// Create a map of node id to flow id and also a subflowInstance lookup map // Create a map of node id to flow id and also a subflowInstance lookup map
var activeNodes = activeFlows[id].getActiveNodes(); var activeNodes = activeFlows[id].getActiveNodes();
Object.keys(activeNodes).forEach(function(nid) { Object.keys(activeNodes).forEach(function(nid) {

View File

@ -70,8 +70,58 @@ function mapEnvVarProperties(obj,prop,flow,config) {
} }
} }
async function evaluateEnvProperties(flow, env, credentials) {
const pendingEvaluations = []
const evaluatedEnv = {}
const envTypes = []
for (let i = 0; i < env.length; i++) {
let { name, value, type } = env[i]
if (type === "env") {
// Do env types last as they may include references to other env vars
// at this level which need to be resolved before they can be looked-up
envTypes.push(env[i])
} else if (type === "bool") {
value = (value === "true") || (value === true);
} else if (type === "cred") {
if (credentials.hasOwnProperty(name)) {
value = credentials[name];
}
} else if (type ==='jsonata') {
pendingEvaluations.push(new Promise((resolve, _) => {
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
if (!err) {
evaluatedEnv[name] = result
}
resolve()
});
}))
} else {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
}
evaluatedEnv[name] = value
}
if (pendingEvaluations.length > 0) {
await Promise.all(pendingEvaluations)
}
for (let i = 0; i < envTypes.length; i++) {
let { name, value, type } = envTypes[i]
// If an env-var wants to lookup itself, delegate straight to the parent
// https://github.com/node-red/node-red/issues/2099
if (value === name) {
value = `$parent.${name}`
}
if (evaluatedEnv.hasOwnProperty(value)) {
value = evaluatedEnv[value]
} else {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
}
evaluatedEnv[name] = value
}
function createNode(flow,config) { return evaluatedEnv
}
async function createNode(flow,config) {
var newNode = null; var newNode = null;
var type = config.type; var type = config.type;
try { try {
@ -140,7 +190,7 @@ function createNode(flow,config) {
// This allows nodes inside the subflow to get ahold of each other // This allows nodes inside the subflow to get ahold of each other
// such as a node accessing its config node // such as a node accessing its config node
flow.subflowInstanceNodes[config.id] = subflow flow.subflowInstanceNodes[config.id] = subflow
subflow.start(); await subflow.start();
return subflow.node; return subflow.node;
} }
} catch(err) { } catch(err) {
@ -150,123 +200,122 @@ function createNode(flow,config) {
} }
function parseConfig(config) { function parseConfig(config) {
var flow = {}; var flow = {};
flow.allNodes = {}; flow.allNodes = {};
flow.subflows = {}; flow.subflows = {};
flow.configs = {}; flow.configs = {};
flow.flows = {}; flow.flows = {};
flow.groups = {}; flow.missingTypes = [];
flow.missingTypes = [];
config.forEach(function(n) { config.forEach(function (n) {
flow.allNodes[n.id] = clone(n); flow.allNodes[n.id] = clone(n);
if (n.type === 'tab') { if (n.type === 'tab') {
flow.flows[n.id] = n; flow.flows[n.id] = n;
flow.flows[n.id].subflows = {}; flow.flows[n.id].subflows = {};
flow.flows[n.id].configs = {}; flow.flows[n.id].configs = {};
flow.flows[n.id].nodes = {}; flow.flows[n.id].nodes = {};
} flow.flows[n.id].groups = {};
if (n.type === 'group') { } else if (n.type === 'subflow') {
flow.groups[n.id] = n; flow.subflows[n.id] = n;
} flow.subflows[n.id].configs = {};
}); flow.subflows[n.id].nodes = {};
flow.subflows[n.id].groups = {};
flow.subflows[n.id].instances = [];
}
});
// TODO: why a separate forEach? this can be merged with above var linkWires = {};
config.forEach(function(n) { var linkOutNodes = [];
if (n.type === 'subflow') { config.forEach(function (n) {
flow.subflows[n.id] = n; if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
flow.subflows[n.id].configs = {}; var subflowDetails = subflowInstanceRE.exec(n.type);
flow.subflows[n.id].nodes = {};
flow.subflows[n.id].instances = [];
}
});
var linkWires = {};
var linkOutNodes = [];
config.forEach(function(n) {
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
var subflowDetails = subflowInstanceRE.exec(n.type);
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { if ((subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type))) {
if (flow.missingTypes.indexOf(n.type) === -1) { if (flow.missingTypes.indexOf(n.type) === -1) {
flow.missingTypes.push(n.type); flow.missingTypes.push(n.type);
} }
} }
var container = null; var container = null;
if (flow.flows[n.z]) { if (flow.flows[n.z]) {
container = flow.flows[n.z]; container = flow.flows[n.z];
} else if (flow.subflows[n.z]) { } else if (flow.subflows[n.z]) {
container = flow.subflows[n.z]; container = flow.subflows[n.z];
} }
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
if (subflowDetails) { if (subflowDetails) {
var subflowType = subflowDetails[1] var subflowType = subflowDetails[1]
n.subflow = subflowType; n.subflow = subflowType;
if (flow.subflows[subflowType]) { if (flow.subflows[subflowType]) {
flow.subflows[subflowType].instances.push(n) flow.subflows[subflowType].instances.push(n)
} }
} }
if (container) { if (container) {
container.nodes[n.id] = n; container.nodes[n.id] = n;
} }
} else { } else {
if (container) { if (container) {
container.configs[n.id] = n; container.configs[n.id] = n;
} else { } else {
flow.configs[n.id] = n; flow.configs[n.id] = n;
flow.configs[n.id]._users = []; flow.configs[n.id]._users = [];
} }
} }
if (n.type === 'link in' && n.links) { if (n.type === 'link in' && n.links) {
// Ensure wires are present in corresponding link out nodes // Ensure wires are present in corresponding link out nodes
n.links.forEach(function(id) { n.links.forEach(function (id) {
linkWires[id] = linkWires[id]||{}; linkWires[id] = linkWires[id] || {};
linkWires[id][n.id] = true; linkWires[id][n.id] = true;
}) })
} else if (n.type === 'link out' && n.links) { } else if (n.type === 'link out' && n.links) {
linkWires[n.id] = linkWires[n.id]||{}; linkWires[n.id] = linkWires[n.id] || {};
n.links.forEach(function(id) { n.links.forEach(function (id) {
linkWires[n.id][id] = true; linkWires[n.id][id] = true;
}) })
linkOutNodes.push(n); linkOutNodes.push(n);
} }
} } else if (n.type === 'group') {
}); const parentContainer = flow.flows[n.z] || flow.subflows[n.z]
linkOutNodes.forEach(function(n) { if (parentContainer) {
var links = linkWires[n.id]; parentContainer.groups[n.id] = n
var targets = Object.keys(links); }
n.wires = [targets]; }
}); });
linkOutNodes.forEach(function (n) {
var links = linkWires[n.id];
var targets = Object.keys(links);
n.wires = [targets];
});
var addedTabs = {}; var addedTabs = {};
config.forEach(function(n) { config.forEach(function (n) {
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
for (var prop in n) { for (var prop in n) {
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
// This property references a global config node // This property references a global config node
flow.configs[n[prop]]._users.push(n.id) flow.configs[n[prop]]._users.push(n.id)
} }
} }
if (n.z && !flow.subflows[n.z]) { if (n.z && !flow.subflows[n.z]) {
if (!flow.flows[n.z]) { if (!flow.flows[n.z]) {
flow.flows[n.z] = {type:'tab',id:n.z}; flow.flows[n.z] = { type: 'tab', id: n.z };
flow.flows[n.z].subflows = {}; flow.flows[n.z].subflows = {};
flow.flows[n.z].configs = {}; flow.flows[n.z].configs = {};
flow.flows[n.z].nodes = {}; flow.flows[n.z].nodes = {};
addedTabs[n.z] = flow.flows[n.z]; addedTabs[n.z] = flow.flows[n.z];
} }
if (addedTabs[n.z]) { if (addedTabs[n.z]) {
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
addedTabs[n.z].nodes[n.id] = n; addedTabs[n.z].nodes[n.id] = n;
} else { } else {
addedTabs[n.z].configs[n.id] = n; addedTabs[n.z].configs[n.id] = n;
} }
} }
} }
} }
}); });
return flow; return flow;
} }
function getGlobalEnv(name) { function getGlobalEnv(name) {
@ -319,10 +368,11 @@ module.exports = {
} }
return undefined; return undefined;
}, },
diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties,
parseConfig: parseConfig, diffNodes,
mapEnvVarProperties,
evaluateEnvProperties,
parseConfig,
diffConfigs: function(oldConfig, newConfig) { diffConfigs: function(oldConfig, newConfig) {
var id; var id;
@ -615,5 +665,6 @@ module.exports = {
* @param {object} config The node configuration object * @param {object} config The node configuration object
* @return {Node} The instance of the node * @return {Node} The instance of the node
*/ */
createNode: createNode createNode: createNode,
evaluateEnvProperties
} }

View File

@ -548,11 +548,9 @@ function getSetting(node, name, flow_) {
if (flow) { if (flow) {
if (node && node.g) { if (node && node.g) {
const group = flow.getGroupNode(node.g); const group = flow.getGroupNode(node.g);
const [result, newName] = flow.getGroupEnvSetting(node, group, name); if (group) {
if (result) { return group.getSetting(name)
return result.val;
} }
name = newName;
} }
return flow.getSetting(name); return flow.getSetting(name);
} }

View File

@ -16,7 +16,6 @@
const path = require("path"); const path = require("path");
const fs = require("fs");
const PACKAGE_ROOT = "../../../packages/node_modules"; const PACKAGE_ROOT = "../../../packages/node_modules";
@ -27,5 +26,10 @@ module.exports = {
}, },
resolve: function(file) { resolve: function(file) {
return path.resolve(path.join(__dirname,PACKAGE_ROOT,file)); return path.resolve(path.join(__dirname,PACKAGE_ROOT,file));
},
sleep: async (time) => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
} }
} }

View File

@ -22,7 +22,9 @@ var helper = require("node-red-node-test-helper");
describe('inject node', function() { describe('inject node', function() {
beforeEach(function(done) { beforeEach(function(done) {
helper.startServer(done); helper.startServer(() => {
done()
});
}); });
function initContext(done) { function initContext(done) {
@ -41,7 +43,7 @@ describe('inject node', function() {
}); });
} }
afterEach(function(done) { afterEach(async function() {
helper.unload().then(function () { helper.unload().then(function () {
return Context.clean({allNodes: {}}); return Context.clean({allNodes: {}});
}).then(function () { }).then(function () {
@ -53,8 +55,11 @@ describe('inject node', function() {
function basicTest(type, val, rval) { function basicTest(type, val, rval) {
it('inject value ('+type+')', function (done) { it('inject value ('+type+')', function (done) {
var flow = [{id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"}, var flow = [
{id: "n2", type: "helper"}]; {id:'flow', type:'tab'},
{id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper", z:'flow'}
];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -93,6 +98,7 @@ describe('inject node', function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function (msg) { n2.on("input", function (msg) {
delete process.env.NR_TEST
try { try {
msg.should.have.property("topic", "t1"); msg.should.have.property("topic", "t1");
msg.should.have.property("payload", "foo"); msg.should.have.property("payload", "foo");
@ -101,13 +107,13 @@ describe('inject node', function() {
done(err); done(err);
} }
}); });
process.env.NR_TEST = 'foo'; process.env.NR_TEST = 'foo';
n1.receive({}); n1.receive({});
}); });
}); });
it('inject name of node as environment variable ', function (done) { it('inject name of node as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -125,7 +131,7 @@ describe('inject node', function() {
}); });
it('inject id of node as environment variable ', function (done) { it('inject id of node as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -143,7 +149,7 @@ describe('inject node', function() {
}); });
it('inject path of node as environment variable ', function (done) { it('inject path of node as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -162,7 +168,7 @@ describe('inject node', function() {
it('inject name of flow as environment variable ', function (done) { it('inject name of flow as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}, {id: "n2", type: "helper"},
{id: "flow", type: "tab", label: "FLOW" }, {id: "flow", type: "tab", label: "FLOW" },
]; ];
@ -182,7 +188,7 @@ describe('inject node', function() {
}); });
it('inject id of flow as environment variable ', function (done) { it('inject id of flow as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}, {id: "n2", type: "helper"},
{id: "flow", type: "tab", name: "FLOW" }, {id: "flow", type: "tab", name: "FLOW" },
]; ];
@ -202,9 +208,10 @@ describe('inject node', function() {
}); });
it('inject name of group as environment variable ', function (done) { it('inject name of group as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, var flow = [{id: "flow", type: "tab" },
{id: "n2", type: "helper"}, {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
{id: "g0", type: "group", name: "GROUP" }, {id: "n2", type: "helper", z: "flow"},
{id: "g0", type: "group", name: "GROUP", z: "flow" },
]; ];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -222,9 +229,10 @@ describe('inject node', function() {
}); });
it('inject id of group as environment variable ', function (done) { it('inject id of group as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, var flow = [{id: "flow", type: "tab" },
{id: "n2", type: "helper"}, {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
{id: "g0", type: "group", name: "GROUP" }, {id: "n2", type: "helper", z: "flow"},
{id: "g0", type: "group", name: "GROUP", z: "flow" },
]; ];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -243,8 +251,9 @@ describe('inject node', function() {
it('inject name of node as environment variable by substitution ', function (done) { it('inject name of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, var flow = [{id: "flow", type: "tab" },
{id: "n2", type: "helper"}]; {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper", z: "flow"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -261,7 +270,7 @@ describe('inject node', function() {
}); });
it('inject id of node as environment variable by substitution ', function (done) { it('inject id of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -279,7 +288,7 @@ describe('inject node', function() {
}); });
it('inject path of node as environment variable by substitution ', function (done) { it('inject path of node as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}]; {id: "n2", type: "helper"}];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -298,7 +307,7 @@ describe('inject node', function() {
it('inject name of flow as environment variable by substitution ', function (done) { it('inject name of flow as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}, {id: "n2", type: "helper"},
{id: "flow", type: "tab", label: "FLOW" }, {id: "flow", type: "tab", label: "FLOW" },
]; ];
@ -318,7 +327,7 @@ describe('inject node', function() {
}); });
it('inject id of flow as environment variable ', function (done) { it('inject id of flow as environment variable ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
{id: "n2", type: "helper"}, {id: "n2", type: "helper"},
{id: "flow", type: "tab", name: "FLOW" }, {id: "flow", type: "tab", name: "FLOW" },
]; ];
@ -338,9 +347,10 @@ describe('inject node', function() {
}); });
it('inject name of group as environment variable by substitution ', function (done) { it('inject name of group as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, var flow = [{id: "flow", type: "tab" },
{id: "n2", type: "helper"}, {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
{id: "g0", type: "group", name: "GROUP" }, {id: "n2", type: "helper", z: "flow"},
{id: "g0", type: "group", name: "GROUP", z: "flow" },
]; ];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -358,9 +368,10 @@ describe('inject node', function() {
}); });
it('inject id of group as environment variable by substitution ', function (done) { it('inject id of group as environment variable by substitution ', function (done) {
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, var flow = [{id: "flow", type: "tab" },
{id: "n2", type: "helper"}, {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
{id: "g0", type: "group", name: "GROUP" }, {id: "n2", type: "helper", z: "flow"},
{id: "g0", type: "group", name: "GROUP", z: "flow" },
]; ];
helper.load(injectNode, flow, function () { helper.load(injectNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");

View File

@ -568,11 +568,12 @@ describe('change Node', function() {
it('sets the value using env property from group', function(done) { it('sets the value using env property from group', function(done) {
var flow = [ var flow = [
{"id": "flow", type:"tab"},
{"id":"group1","type":"group","env":[ {"id":"group1","type":"group","env":[
{"name":"NR_TEST_A", "value":"bar", "type": "str"} {"name":"NR_TEST_A", "value":"bar", "type": "str"}
]}, ], z: "flow"},
{"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, {"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"},
{id:"helperNode1", type:"helper", wires:[]} {id:"helperNode1", type:"helper", wires:[], z: "flow"}
]; ];
helper.load(changeNode, flow, function() { helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1"); var changeNode1 = helper.getNode("changeNode1");
@ -591,12 +592,13 @@ describe('change Node', function() {
it('sets the value using env property from nested group', function(done) { it('sets the value using env property from nested group', function(done) {
var flow = [ var flow = [
{"id": "flow", type:"tab"},
{"id":"group1","type":"group","env":[ {"id":"group1","type":"group","env":[
{"name":"NR_TEST_A", "value":"bar", "type": "str"} {"name":"NR_TEST_A", "value":"bar", "type": "str"}
]}, ], z: "flow"},
{"id":"group2","type":"group","g":"group1","env":[]}, {"id":"group2","type":"group","g":"group1","env":[], z: "flow"},
{"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, {"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"},
{id:"helperNode1", type:"helper", wires:[]} {id:"helperNode1", type:"helper", wires:[], z: "flow"}
]; ];
helper.load(changeNode, flow, function() { helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1"); var changeNode1 = helper.getNode("changeNode1");

View File

@ -253,35 +253,32 @@ describe('subflow', function() {
it('should access typed value of env var', function(done) { it('should access typed value of env var', function(done) {
var flow = [ var flow = [
{id:"t0", type:"tab", label:"", disabled:false, info:""}, { id: "t0", type: "tab", label: "", disabled: false, info: "" },
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", {
env: [ id: "n1", x: 10, y: 10, z: "t0", type: "subflow:s1",
{name: "KN", type: "num", value: "100"}, env: [
{name: "KB", type: "bool", value: "true"}, { name: "KN", type: "num", value: "100" },
{name: "KJ", type: "json", value: "[1,2,3]"}, { name: "KB", type: "bool", value: "true" },
{name: "Kb", type: "bin", value: "[65,65]"}, { name: "KJ", type: "json", value: "[1,2,3]" },
{name: "Ke", type: "env", value: "KS"}, { name: "Kb", type: "bin", value: "[65,65]" },
{name: "Kj", type: "jsonata", value: "1+2"}, { name: "Ke", type: "env", value: "KS" },
], { name: "Kj", type: "jsonata", value: "1+2" },
wires:[["n2"]]}, ],
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, wires: [["n2"]]
// Subflow
{id:"s1", type:"subflow", name:"Subflow", info:"",
in:[{
x:10, y:10,
wires:[ {id:"s1-n1"} ]
}],
out:[{
x:10, y:10,
wires:[ {id:"s1-n1", port:0} ]
}],
env: [
{name: "KS", type: "str", value: "STR"}
]
}, },
{id:"s1-n1", x:10, y:10, z:"s1", type:"function", { id: "n2", x: 10, y: 10, z: "t0", type: "helper", wires: [] },
func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;", // Subflow
wires:[]} {
id: "s1", type: "subflow", name: "Subflow", info: "",
in: [{ x: 10, y: 10, wires: [{ id: "s1-n1" }] }],
out: [{ x: 10, y: 10, wires: [{ id: "s1-n1", port: 0 }] }],
env: [{ name: "KS", type: "str", value: "STR" }]
},
{
id: "s1-n1", x: 10, y: 10, z: "s1", type: "function",
func: "msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;",
wires: []
}
]; ];
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
const should = require("should");
const NR_TEST_UTILS = require("nr-test-utils");
const { Group } = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Group");
describe('Group', function () {
describe('getSetting', function () {
it("returns group name/id", async function () {
const group = new Group({
getSetting: v => v+v
}, {
name: "g1",
id: "group1"
})
await group.start()
group.getSetting("NR_GROUP_NAME").should.equal("g1")
group.getSetting("NR_GROUP_ID").should.equal("group1")
})
it("delegates to parent if not found", async function () {
const group = new Group({
getSetting: v => v+v
}, {
name: "g1",
id: "group1"
})
await group.start()
group.getSetting("123").should.equal("123123")
})
it("delegates to parent if explicit requested", async function () {
const parentGroup = new Group({
getSetting: v => v+v
}, {
name: "g0",
id: "group0"
})
const group = new Group(parentGroup, {
name: "g1",
id: "group1"
})
await parentGroup.start()
await group.start()
group.getSetting("$parent.NR_GROUP_NAME").should.equal("g0")
group.getSetting("$parent.NR_GROUP_ID").should.equal("group0")
})
})
})

View File

@ -68,11 +68,13 @@ describe('Subflow', function() {
this.handled = 0; this.handled = 0;
this.stopped = false; this.stopped = false;
this.received = null; this.received = null;
this.receivedEnv = null;
currentNodes[node.id] = node; currentNodes[node.id] = node;
this.on('input',function(msg) { this.on('input',function(msg) {
// console.log(this.id,msg.payload); // console.log(this.id,msg.payload);
node.handled++; node.handled++;
node.received = msg.payload; node.received = msg.payload;
node.receivedEnv = msg.receivedEnv;
node.send(msg); node.send(msg);
}); });
this.on('close',function() { this.on('close',function() {
@ -185,7 +187,15 @@ describe('Subflow', function() {
var flow = node._flow; var flow = node._flow;
var val = flow.getSetting("__KEY__"); var val = flow.getSetting("__KEY__");
node.received = val; node.received = val;
node.send({payload: val}); const receivedEnv = {}
try {
['__KEY__','__KEY1__','__KEY2__','__KEY3__','__KEY4__'].forEach(k => {
receivedEnv[k] = flow.getSetting(k)
})
} catch (err) {
console.log(err)
}
node.send({payload: val, receivedEnv});
}); });
this.on('close',function() { this.on('close',function() {
node.stopped = true; node.stopped = true;
@ -282,7 +292,7 @@ describe('Subflow', function() {
getType.restore(); getType.restore();
}); });
describe('#start',function() { describe('#start',function() {
it("instantiates a subflow and stops it",function(done) { it("instantiates a subflow and stops it", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
@ -297,7 +307,7 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]); var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(4); Object.keys(activeNodes).should.have.length(4);
@ -333,36 +343,20 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
await flow.stop()
flow.stop().then(function() { Object.keys(currentNodes).should.have.length(0);
Object.keys(currentNodes).should.have.length(0); Object.keys(stoppedNodes).should.have.length(6);
Object.keys(stoppedNodes).should.have.length(6);
// currentNodes.should.not.have.a.property("1");
// currentNodes.should.not.have.a.property("3");
// currentNodes.should.not.have.a.property("4");
// // currentNodes.should.not.have.a.property(sfInstanceId);
// // currentNodes.should.not.have.a.property(sfInstanceId2);
// // currentNodes.should.not.have.a.property(sfConfigId);
// stoppedNodes.should.have.a.property("1");
// stoppedNodes.should.have.a.property("3");
// stoppedNodes.should.have.a.property("4");
// // stoppedNodes.should.have.a.property(sfInstanceId);
// // stoppedNodes.should.have.a.property(sfInstanceId2);
// // stoppedNodes.should.have.a.property(sfConfigId);
done();
});
},150);
}); });
it("instantiates a subflow inside a subflow and stops it",function(done) {
it("instantiates a subflow inside a subflow and stops it", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
@ -379,24 +373,20 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
currentNodes["1"].should.have.a.property("handled",0); currentNodes["1"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0);
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
await NR_TEST_UTILS.sleep(150)
setTimeout(function() { currentNodes["1"].should.have.a.property("handled",1);
currentNodes["1"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); await flow.stop()
flow.stop().then(function() { Object.keys(currentNodes).should.have.length(0);
Object.keys(currentNodes).should.have.length(0);
done();
});
},150);
}); });
it("rewires a subflow node on update/start",function(done){
it("rewires a subflow node on update/start", async function(){
var rawConfig = [ var rawConfig = [
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
@ -417,7 +407,7 @@ describe('Subflow', function() {
var diff = flowUtils.diffConfigs(config,newConfig); var diff = flowUtils.diffConfigs(config,newConfig);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(4); Object.keys(activeNodes).should.have.length(4);
@ -429,36 +419,28 @@ describe('Subflow', function() {
currentNodes["4"].should.have.a.property("handled",0); currentNodes["4"].should.have.a.property("handled",0);
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
await NR_TEST_UTILS.sleep(150)
currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",0);
setTimeout(function() { flow.update(newConfig,newConfig.flows["t1"]);
currentNodes["1"].should.have.a.property("handled",1); await flow.start(diff)
// currentNodes[sfInstanceId].should.have.a.property("handled",1); currentNodes["1"].receive({payload:"test2"});
// currentNodes[sfInstanceId2].should.have.a.property("handled",1); await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",2);
currentNodes["4"].should.have.a.property("handled",0); // currentNodes[sfInstanceId].should.have.a.property("handled",2);
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
flow.update(newConfig,newConfig.flows["t1"]); await flow.stop()
flow.start(diff)
currentNodes["1"].receive({payload:"test2"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",2);
// currentNodes[sfInstanceId].should.have.a.property("handled",2);
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
flow.stop().then(function() {
done();
});
},150);
},150);
}); });
}); });
describe('#stop', function() { describe('#stop', function() {
it("stops subflow instance nodes",function(done) { it("stops subflow instance nodes", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
@ -470,20 +452,18 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(3); Object.keys(activeNodes).should.have.length(3);
Object.keys(stoppedNodes).should.have.length(0); Object.keys(stoppedNodes).should.have.length(0);
flow.stop(["2"]).then(function() { await flow.stop(["2"])
Object.keys(currentNodes).should.have.length(2); Object.keys(currentNodes).should.have.length(2);
Object.keys(stoppedNodes).should.have.length(1); Object.keys(stoppedNodes).should.have.length(1);
done();
}).catch(done);
}); });
}); });
describe("#handleStatus",function() { describe("#handleStatus",function() {
it("passes a status event to the subflow's parent tab status node - all scope",function(done) { it("passes a status event to the subflow's parent tab status node - all scope", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -496,27 +476,24 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test status"); statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus"); statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node"); statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() { await flow.stop()
done();
});
},150);
}); });
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { it("passes a status event to the subflow's parent tab status node - targetted scope", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -531,34 +508,30 @@ describe('Subflow', function() {
var flow = Flow.create({handleStatus:() => { parentFlowStatusCalled = true} },config,config.flows["t1"]); var flow = Flow.create({handleStatus:() => { parentFlowStatusCalled = true} },config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
parentFlowStatusCalled.should.be.false(); parentFlowStatusCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test status"); statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus"); statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node"); statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() { await flow.stop()
done();
});
},150);
}); });
}); });
describe("status node", function() { describe("status node", function() {
it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -578,29 +551,24 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test-payload"}); activeNodes["1"].receive({payload:"test-payload"});
await NR_TEST_UTILS.sleep(150)
setTimeout(function() { currentNodes["sn"].should.have.a.property("handled",1);
currentNodes["sn"].should.have.a.property("handled",1); var statusMessage = currentNodes["sn"].messages[0];
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test-payload"); statusMessage.status.should.have.a.property("text","test-payload");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2"); statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1"); statusMessage.status.source.should.have.a.property("type","subflow:sf1");
await flow.stop()
flow.stop().then(function() {
done();
});
},150);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -620,29 +588,26 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:{text:"payload-obj"}}); activeNodes["1"].receive({payload:{text:"payload-obj"}});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); currentNodes["sn"].should.have.a.property("handled",1);
statusMessage.status.should.have.a.property("text","payload-obj"); var statusMessage = currentNodes["sn"].messages[0];
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() { statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","payload-obj");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
done(); await flow.stop()
});
},150);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.status", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -662,29 +627,26 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({status:{text:"status-obj"}}); activeNodes["1"].receive({status:{text:"status-obj"}});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); currentNodes["sn"].should.have.a.property("handled",1);
statusMessage.status.should.have.a.property("text","status-obj"); var statusMessage = currentNodes["sn"].messages[0];
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() { statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","status-obj");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
done(); flow.stop()
});
},150);
}); });
it("does not emit a regular status event if it contains a subflow-status node", function(done) { it("does not emit a regular status event if it contains a subflow-status node", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -704,7 +666,7 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
@ -712,15 +674,12 @@ describe('Subflow', function() {
currentNodes["sn"].should.have.a.property("handled",0); currentNodes["sn"].should.have.a.property("handled",0);
flow.stop().then(function() { await flow.stop()
done();
});
}); });
}) })
describe("#handleError",function() { describe("#handleError",function() {
it("passes an error event to the subflow's parent tab catch node - all scope",function(done) { it("passes an error event to the subflow's parent tab catch node - all scope",async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -733,28 +692,26 @@ describe('Subflow', function() {
]); ]);
var flow = Flow.create({},config,config.flows["t1"]); var flow = Flow.create({},config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error"); currentNodes["sn"].should.have.a.property("handled",1);
statusMessage.error.should.have.a.property("message","test error"); var statusMessage = currentNodes["sn"].messages[0];
statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() { statusMessage.should.have.a.property("error");
done(); statusMessage.error.should.have.a.property("message","test error");
}); statusMessage.error.should.have.a.property("source");
},150); statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
await flow.stop()
}); });
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { it("passes an error event to the subflow's parent tab catch node - targetted scope", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
@ -768,50 +725,31 @@ describe('Subflow', function() {
var parentFlowErrorCalled = false; var parentFlowErrorCalled = false;
var flow = Flow.create({handleError:() => { parentFlowErrorCalled = true} },config,config.flows["t1"]); var flow = Flow.create({handleError:() => { parentFlowErrorCalled = true} },config,config.flows["t1"]);
flow.start(); await flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
parentFlowErrorCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); parentFlowErrorCalled.should.be.false();
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error"); currentNodes["sn"].should.have.a.property("handled",1);
statusMessage.error.should.have.a.property("message","test error"); var statusMessage = currentNodes["sn"].messages[0];
statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() { statusMessage.should.have.a.property("error");
done(); statusMessage.error.should.have.a.property("message","test error");
}); statusMessage.error.should.have.a.property("source");
},150); statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node");
await flow.stop()
}); });
}); });
describe("#env var", function() { describe("#env var", function() {
// should be changed according to internal env var representation it("can access process env var", async function() {
function setEnv(node, key, val) {
var flow = node._flow;
if (flow) {
var env = flow.env;
if (!env) {
env = flow.env = {};
}
env[key] = {
name: key,
type: "str",
value: val
};
}
}
it("can access process env var", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
@ -828,29 +766,25 @@ describe('Subflow', function() {
handleError: (a,b,c) => { console.log(a,b,c); } handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]); },config,config.flows["t1"]);
flow.start(); await flow.start();
process.env["__KEY__"] = "__VAL__"; process.env["__KEY__"] = "__VAL__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("received", "__VAL__"); currentNodes["3"].should.have.a.property("received", "__VAL__");
await flow.stop()
flow.stop().then(function() {
done();
});
},150);
}); });
it("can access subflow env var", function(done) { it("can access subflow env var", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
{id:"sf1",type:"subflow",name:"Subflow 2",info:"", {id:"sf1",type:"subflow",name:"Subflow 2",info:"",env: [{name: '__KEY__', value: '__VAL1__', type: 'str'}],
"in":[ {wires:[{id:"sf1-1"}]} ], "in":[ {wires:[{id:"sf1-1"}]} ],
"out":[ {wires:[{id:"sf1-2",port:0}]} ]}, "out":[ {wires:[{id:"sf1-2",port:0}]} ]},
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
{id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]} {id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]}
]); ]);
@ -859,7 +793,7 @@ describe('Subflow', function() {
handleError: (a,b,c) => { console.log(a,b,c); } handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]); },config,config.flows["t1"]);
flow.start(); await flow.start();
var testenv_node = null; var testenv_node = null;
for (var n in currentNodes) { for (var n in currentNodes) {
@ -870,32 +804,30 @@ describe('Subflow', function() {
} }
} }
process.env["__KEY__"] = "__VAL0__"; process.env["__KEY__"] = "__VAL0__";
setEnv(testenv_node, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("received", "__VAL1__");
flow.stop().then(function() { currentNodes["3"].should.have.a.property("received", "__VAL1__");
done(); await flow.stop()
});
},150);
}); });
it("can access nested subflow env var", function(done) { it("can access nested subflow env var", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab", env: [{name: '__KEY1__', value: 't1', type: 'str'}]},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
{id:"sf1",type:"subflow",name:"Subflow 1",info:"", {id:"sf1",type:"subflow",name:"Subflow 1",info:"",
in:[{wires:[{id:"sf1-1"}]}], env: [{name: '__KEY2__', value: 'sf1', type: 'str'}],
out:[{wires:[{id:"sf1-2",port:0}]}]}, in:[{wires:[{id:"sf1-1"}]}],
out:[{wires:[{id:"sf1-2",port:0}]}]},
{id:"sf2",type:"subflow",name:"Subflow 2",info:"", {id:"sf2",type:"subflow",name:"Subflow 2",info:"",
in:[{wires:[{id:"sf2-1"}]}], env: [{name: '__KEY3__', value: 'sf2', type: 'str'}],
out:[{wires:[{id:"sf2-2",port:0}]}]}, in:[{wires:[{id:"sf2-1"}]}],
out:[{wires:[{id:"sf2-2",port:0}]}]},
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
{id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]]}, {id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]], env: [{name: '__KEY4__', value: 'sf1-2', type: 'str'}] },
{id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]}, {id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]},
{id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]}, {id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]},
]); ]);
@ -904,45 +836,22 @@ describe('Subflow', function() {
handleError: (a,b,c) => { console.log(a,b,c); } handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]); },config,config.flows["t1"]);
flow.start(); await flow.start();
var node_sf1_1 = null;
var node_sf2_1 = null;
var testenv_node = null;
for (var n in currentNodes) {
var node = currentNodes[n];
if (node.foo === "sf1.1") {
node_sf1_1 = node;
}
if (node.foo === "sf2.1") {
node_sf2_1 = node;
}
}
process.env["__KEY__"] = "__VAL0__"; process.env["__KEY__"] = "__VAL0__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("received", "__VAL0__"); currentNodes["3"].should.have.a.property("receivedEnv");
currentNodes["3"].receivedEnv.should.have.a.property('__KEY__', '__VAL0__')
currentNodes["3"].receivedEnv.should.have.a.property('__KEY1__', 't1')
currentNodes["3"].receivedEnv.should.have.a.property('__KEY2__', 'sf1')
currentNodes["3"].receivedEnv.should.have.a.property('__KEY3__', 'sf2')
currentNodes["3"].receivedEnv.should.have.a.property('__KEY4__', 'sf1-2')
setEnv(node_sf1_1, "__KEY__", "__VAL1__"); await flow.stop()
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__");
setEnv(node_sf2_1, "__KEY__", "__VAL2__");
currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL2__");
flow.stop().then(function() {
done();
});
},150);
},150);
},150);
}); });
it("can access name of subflow as env var", function(done) { it("can access name of subflow as env var", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
@ -959,19 +868,15 @@ describe('Subflow', function() {
handleError: (a,b,c) => { console.log(a,b,c); } handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]); },config,config.flows["t1"]);
flow.start(); await flow.start();
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("received", "SFN"); currentNodes["3"].should.have.a.property("received", "SFN");
await flow.stop()
flow.stop().then(function() {
done();
});
},150);
}); });
it("can access id of subflow as env var", function(done) { it("can access id of subflow as env var", async function() {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
@ -988,19 +893,13 @@ describe('Subflow', function() {
handleError: (a,b,c) => { console.log(a,b,c); } handleError: (a,b,c) => { console.log(a,b,c); }
},config,config.flows["t1"]); },config,config.flows["t1"]);
flow.start(); await flow.start();
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() { await NR_TEST_UTILS.sleep(150)
currentNodes["3"].should.have.a.property("received", "2"); currentNodes["3"].should.have.a.property("received", "2");
await flow.stop()
flow.stop().then(function() {
done();
});
},150);
}); });
}); });
}); });

View File

@ -93,7 +93,7 @@ describe('flows/index', function() {
flowCreate.flows[id] = { flowCreate.flows[id] = {
flow: flow, flow: flow,
global: global, global: global,
start: sinon.spy(), start: sinon.spy(async() => {}),
update: sinon.spy(), update: sinon.spy(),
stop: sinon.spy(), stop: sinon.spy(),
getActiveNodes: function() { getActiveNodes: function() {
@ -221,13 +221,18 @@ describe('flows/index', function() {
return Promise.resolve({flows:originalConfig}); return Promise.resolve({flows:originalConfig});
} }
events.once('flows:started',function() { events.once('flows:started',function() {
flows.setFlows(newConfig,"nodes").then(function() { events.once('flows:started', function() {
flows.getFlows().flows.should.eql(newConfig); try {
flowCreate.flows['t1'].update.called.should.be.true(); flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t2'].start.called.should.be.true(); flowCreate.flows['t1'].update.called.should.be.true();
flowCreate.flows['_GLOBAL_'].update.called.should.be.true(); flowCreate.flows['t2'].start.called.should.be.true();
done(); flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
done();
} catch(err) {
done(err)
}
}) })
flows.setFlows(newConfig,"nodes")
}); });
flows.init({log:mockLog, settings:{},storage:storage}); flows.init({log:mockLog, settings:{},storage:storage});
@ -250,13 +255,14 @@ describe('flows/index', function() {
} }
events.once('flows:started',function() { events.once('flows:started',function() {
flows.setFlows(newConfig,"nodes").then(function() { events.once('flows:started',function() {
flows.getFlows().flows.should.eql(newConfig); flows.getFlows().flows.should.eql(newConfig);
flowCreate.flows['t1'].update.called.should.be.true(); flowCreate.flows['t1'].update.called.should.be.true();
flowCreate.flows['t2'].start.called.should.be.true(); flowCreate.flows['t2'].start.called.should.be.true();
flowCreate.flows['_GLOBAL_'].update.called.should.be.true(); flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
flows.stopFlows().then(done); flows.stopFlows().then(done);
}) })
flows.setFlows(newConfig,"nodes")
}); });
flows.init({log:mockLog, settings:{},storage:storage}); flows.init({log:mockLog, settings:{},storage:storage});

View File

@ -149,7 +149,7 @@ describe('flows/util', function() {
{id:"t1",type:"tab"} {id:"t1",type:"tab"}
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; 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":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig); parsedConfig.should.eql(expectedConfig);
}); });
@ -160,7 +160,7 @@ describe('flows/util', function() {
{id:"t1",type:"tab"} {id:"t1",type:"tab"}
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; 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":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig); parsedConfig.should.eql(expectedConfig);
}); });
@ -172,7 +172,7 @@ describe('flows/util', function() {
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]} {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; 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":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig); parsedConfig.should.eql(expectedConfig);
}); });
@ -184,7 +184,7 @@ describe('flows/util', function() {
{id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]} {id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]}
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]}; 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":{},"groups":{},"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":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig); parsedConfig.should.eql(expectedConfig);
}); });
@ -196,7 +196,7 @@ describe('flows/util', function() {
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
parsedConfig.missingTypes.should.eql(['missing']); parsedConfig.missingTypes.should.eql(['missing']);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]}; 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":{},"groups":{},"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(); redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true();
}); });
@ -206,7 +206,7 @@ describe('flows/util', function() {
{id:"cn",type:"test"}, {id:"cn",type:"test"},
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; 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); parsedConfig.should.eql(expectedConfig);
}); });
@ -217,7 +217,7 @@ describe('flows/util', function() {
{id:"g1",type:"group",z:"t1"} {id:"g1",type:"group",z:"t1"}
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]} var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]}
parsedConfig.should.eql(expectedConfig); parsedConfig.should.eql(expectedConfig);
}); });