diff --git a/red/runtime/index.js b/red/runtime/index.js index 6577ee59c..e8af69712 100644 --- a/red/runtime/index.js +++ b/red/runtime/index.js @@ -90,6 +90,7 @@ function start() { }) .then(function() { return storage.init(runtime)}) .then(function() { return settings.load(storage)}) + .then(function() { return redNodes.loadContextsPlugin()}) .then(function() { if (log.metric()) { @@ -229,7 +230,9 @@ function stop() { clearTimeout(reinstallTimeout); } started = false; - return redNodes.stopFlows(); + return redNodes.stopFlows().then(function(){ + return redNodes.closeContextsPlugin(); + }); } var runtime = module.exports = { diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index 6e9f057ae..1413ce523 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -155,5 +155,13 @@ "readme": "### About\n\nThis is your project's README.md file. It helps users understand what your\nproject does, how to use it and anything else they may need to know." } } + }, + + "context": { + "error-module-not-loaded": "'__module__' could not be loaded", + "error-module-not-defined": "'module' is not defined in '__storage__' of settings.contextStorage", + "error-invalid-default-module": "Invalid storage '__storage__' is specified as a default storage", + "error-use-undefined-storage": "Undefined storage '__storage__' is specified" } + } diff --git a/red/runtime/nodes/Node.js b/red/runtime/nodes/Node.js index b8cfa6532..308d30125 100644 --- a/red/runtime/nodes/Node.js +++ b/red/runtime/nodes/Node.js @@ -105,12 +105,12 @@ Node.prototype.close = function(removed) { if (promises.length > 0) { return when.settle(promises).then(function() { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } }); } else { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } return; } diff --git a/red/runtime/nodes/context.js b/red/runtime/nodes/context.js deleted file mode 100644 index 19c929649..000000000 --- a/red/runtime/nodes/context.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var clone = require("clone"); -var when = require("when"); -var util = require("../util"); - -function createContext(id,seed) { - var data = seed || {}; - var obj = seed || {}; - obj.get = function get(key) { - return util.getMessageProperty(data,key); - }; - obj.set = function set(key, value) { - util.setMessageProperty(data,key,value); - } - obj.keys = function() { - var keysData = Object.keys(data); - if (seed == null) { - return keysData; - } else { - return keysData.filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys"; - }); - } - } - return obj; -} - -var contexts = {}; -var globalContext = null; - -function getContext(localId,flowId) { - var contextId = localId; - if (flowId) { - contextId = localId+":"+flowId; - } - if (contexts.hasOwnProperty(contextId)) { - return contexts[contextId]; - } - var newContext = createContext(contextId); - if (flowId) { - newContext.flow = getContext(flowId); - } - if (globalContext) { - newContext.global = globalContext; - } - contexts[contextId] = newContext; - return newContext; -} -function deleteContext(id,flowId) { - var contextId = id; - if (flowId) { - contextId = id+":"+flowId; - } - delete contexts[contextId]; -} -function clean(flowConfig) { - var activeIds = {}; - var contextId; - var node; - for (var id in contexts) { - if (contexts.hasOwnProperty(id) && id !== "global") { - var idParts = id.split(":"); - if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { - delete contexts[id]; - } - } - } -} -module.exports = { - init: function(settings) { - globalContext = createContext("global",settings.functionGlobalContext || {}); - contexts['global'] = globalContext; - }, - get: getContext, - delete: deleteContext, - clean:clean -}; diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js new file mode 100644 index 000000000..5c7f6609d --- /dev/null +++ b/red/runtime/nodes/context/index.js @@ -0,0 +1,233 @@ +/** + * 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 clone = require("clone"); +var log = require("../../log"); +var memory = require("./memory"); + +var settings; +var contexts = {}; +var globalContext = null; +var externalContexts = {}; +var noContextStorage = true; + +function init(_settings) { + settings = _settings; + externalContexts = {}; + + // init memory plugin + var seed = settings.functionGlobalContext || {}; + externalContexts["_"] = memory(); + externalContexts["_"].setGlobalContext(seed); + globalContext = createContext("global",seed); + contexts['global'] = globalContext; +} + +function load() { + // load & init plugins in settings.contextStorage + var plugins = settings.contextStorage; + var isAlias = false; + if (plugins) { + var promises = []; + noContextStorage = false; + for(var pluginName in plugins){ + if(pluginName === "_"){ + continue; + } + if(pluginName === "default" && typeof plugins[pluginName] === "string"){ + isAlias = true; + continue; + } + var plugin; + if(plugins[pluginName].hasOwnProperty("module")){ + var config = plugins[pluginName].config || {}; + copySettings(config, settings); + if(typeof plugins[pluginName].module === "string") { + try{ + plugin = require("./"+plugins[pluginName].module); + }catch(err){ + return Promise.reject(new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module}))); + } + } else { + plugin = plugins[pluginName].module; + } + externalContexts[pluginName] = plugin(config); + }else{ + return Promise.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); + } + } + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].open()); + } + } + if(isAlias){ + if(externalContexts.hasOwnProperty(plugins["default"])){ + externalContexts["default"] = externalContexts[plugins["default"]]; + }else{ + return Promise.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); + } + } + return Promise.all(promises); + } else { + noContextStorage = true; + return externalContexts["_"].open(); + } +} + +function copySettings(config, settings){ + var copy = ["userDir"] + config.settings = {}; + copy.forEach(function(setting){ + config.settings[setting] = clone(settings[setting]); + }); +} + +function getContextStorage(storage) { + if (externalContexts.hasOwnProperty(storage)) { + return externalContexts[storage]; + } else if (externalContexts.hasOwnProperty("default")) { + return externalContexts["default"]; + } else { + var contextError = new Error(log._("context.error-use-undefined-storage", {storage:storage})); + contextError.name = "ContextError"; + throw contextError; + } +} + +function createContext(id,seed) { + var scope = id; + var obj = seed || {}; + + obj.get = function(key, storage, callback) { + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (typeof callback !== 'function'){ + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); + } + return context.get(scope, key, callback); + }; + obj.set = function(key, value, storage, callback) { + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (callback && typeof callback !== 'function') { + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); + } + context.set(scope, key, value, callback); + }; + obj.keys = function(storage, callback) { + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (typeof callback !== 'function') { + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); + } + return context.keys(scope, callback); + }; + return obj; +} + +function getContext(localId,flowId) { + var contextId = localId; + if (flowId) { + contextId = localId+":"+flowId; + } + if (contexts.hasOwnProperty(contextId)) { + return contexts[contextId]; + } + var newContext = createContext(contextId); + if (flowId) { + newContext.flow = getContext(flowId); + } + if (globalContext) { + newContext.global = globalContext; + } + contexts[contextId] = newContext; + return newContext; +} + +function deleteContext(id,flowId) { + if(noContextStorage){ + var contextId = id; + if (flowId) { + contextId = id+":"+flowId; + } + delete contexts[contextId]; + return externalContexts["_"].delete(contextId); + }else{ + return Promise.resolve(); + } +} + +function clean(flowConfig) { + var promises = []; + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].clean(Object.keys(flowConfig.allNodes))); + } + } + for (var id in contexts) { + if (contexts.hasOwnProperty(id) && id !== "global") { + var idParts = id.split(":"); + if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { + delete contexts[id]; + } + } + } + return Promise.all(promises); +} + +function close() { + var promises = []; + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].close()); + } + } + return Promise.all(promises); +} + +module.exports = { + init: init, + load: load, + get: getContext, + delete: deleteContext, + clean: clean, + close: close +}; diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js new file mode 100644 index 000000000..fed8ae959 --- /dev/null +++ b/red/runtime/nodes/context/localfilesystem.js @@ -0,0 +1,164 @@ +/** + * 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 fs = require('fs-extra'); +var path = require("path"); +var util = require("../../util"); + +function getStoragePath(storageBaseDir, scope) { + if(scope.indexOf(":") === -1){ + if(scope === "global"){ + return path.join(storageBaseDir,"global",scope); + }else{ // scope:flow + return path.join(storageBaseDir,scope,"flow"); + } + }else{ // scope:local + var ids = scope.split(":") + return path.join(storageBaseDir,ids[1],ids[0]); + } +} + +function getBasePath(config) { + var base = config.base || "contexts"; + var storageBaseDir; + if (!config.dir) { + if(config.settings && config.settings.userDir){ + storageBaseDir = path.join(config.settings.userDir, base); + }else{ + try { + fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json")); + storageBaseDir = path.join(process.env.NODE_RED_HOME, base); + } catch(err) { + try { + // Consider compatibility for older versions + if (process.env.HOMEPATH) { + fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json")); + storageBaseDir = path.join(process.env.HOMEPATH, ".node-red", base); + } + } catch(err) { + } + if (!storageBaseDir) { + storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red", base); + } + } + } + }else{ + storageBaseDir = path.join(config.dir, base); + } + return storageBaseDir; +} + +function loadFile(storagePath){ + return fs.pathExists(storagePath).then(function(exists){ + if(exists === true){ + return fs.readFile(storagePath, "utf8"); + }else{ + return Promise.resolve(undefined); + } + }).catch(function(err){ + throw Promise.reject(err); + }); +} + +function LocalFileSystem(config){ + this.config = config; + this.storageBaseDir = getBasePath(this.config); +} + +LocalFileSystem.prototype.open = function(){ + return Promise.resolve(); +} + +LocalFileSystem.prototype.close = function(){ + return Promise.resolve(); +} + +LocalFileSystem.prototype.get = function(scope, key, callback) { + if(typeof callback !== "function"){ + throw new Error("Callback must be a function"); + } + var storagePath = getStoragePath(this.storageBaseDir ,scope); + loadFile(storagePath + ".json").then(function(data){ + if(data){ + callback(null, util.getMessageProperty(JSON.parse(data),key)); + }else{ + callback(null, undefined); + } + }).catch(function(err){ + callback(err); + }); +}; + +LocalFileSystem.prototype.set =function(scope, key, value, callback) { + var storagePath = getStoragePath(this.storageBaseDir ,scope); + loadFile(storagePath + ".json").then(function(data){ + var obj = data ? JSON.parse(data) : {} + util.setMessageProperty(obj,key,value); + return fs.outputFile(storagePath + ".json", JSON.stringify(obj, undefined, 4), "utf8"); + }).then(function(){ + if(typeof callback === "function"){ + callback(null); + } + }).catch(function(err){ + if(typeof callback === "function"){ + callback(err); + } + }); +}; + +LocalFileSystem.prototype.keys = function(scope, callback){ + if(typeof callback !== "function"){ + throw new Error("Callback must be a function"); + } + var storagePath = getStoragePath(this.storageBaseDir ,scope); + loadFile(storagePath + ".json").then(function(data){ + if(data){ + callback(null, Object.keys(JSON.parse(data))); + }else{ + callback(null, []); + } + }).catch(function(err){ + callback(err); + }); +}; + +LocalFileSystem.prototype.delete = function(scope){ + var storagePath = getStoragePath(this.storageBaseDir ,scope); + return fs.remove(storagePath + ".json"); +} + +LocalFileSystem.prototype.clean = function(activeNodes){ + var self = this; + return fs.readdir(self.storageBaseDir).then(function(dirs){ + return Promise.all(dirs.reduce(function(result, item){ + if(item !== "global" && activeNodes.indexOf(item) === -1){ + result.push(fs.remove(path.join(self.storageBaseDir,item))); + } + return result; + },[])); + }).catch(function(err){ + if(err.code == 'ENOENT') { + return Promise.resolve(); + }else{ + return Promise.reject(err); + } + }); +} + +module.exports = function(config){ + return new LocalFileSystem(config); +}; + diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js new file mode 100644 index 000000000..b6f687f96 --- /dev/null +++ b/red/runtime/nodes/context/memory.js @@ -0,0 +1,118 @@ +/** + * 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 util = require("../../util"); + +function Memory(config){ + this.data = {}; +} + +Memory.prototype.open = function(){ + return Promise.resolve(); +}; + +Memory.prototype.close = function(){ + return Promise.resolve(); +}; + +Memory.prototype.get = function(scope, key, callback) { + var value; + try{ + if(this.data[scope]){ + value = util.getMessageProperty(this.data[scope], key); + } + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } + } + if(callback){ + callback(null, value); + } else { + return value; + } +}; + +Memory.prototype.set =function(scope, key, value, callback) { + if(!this.data[scope]){ + this.data[scope] = {}; + } + try{ + util.setMessageProperty(this.data[scope],key,value); + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } + } + if(callback){ + callback(null); + } +}; + +Memory.prototype.keys = function(scope, callback){ + var values = []; + try{ + if(this.data[scope]){ + if (scope !== "global") { + values = Object.keys(this.data[scope]); + } else { + values = Object.keys(this.data[scope]).filter(function (key) { + return key !== "set" && key !== "get" && key !== "keys"; + }); + } + } + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } + } + if(callback){ + callback(null, values); + } else { + return values; + } +}; + +Memory.prototype.delete = function(scope){ + delete this.data[scope]; + return Promise.resolve(); +}; + +Memory.prototype.clean = function(activeNodes){ + for(var id in this.data){ + if(this.data.hasOwnProperty(id) && id !== "global"){ + var idParts = id.split(":"); + if(activeNodes.indexOf(idParts[0]) === -1){ + delete this.data[id]; + } + } + } + return Promise.resolve(); +} + +Memory.prototype.setGlobalContext= function(seed){ + this.data["global"] = seed; +}; + +module.exports = function(config){ + return new Memory(config); +}; \ No newline at end of file diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 1b2a87ac0..0f12e4746 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -160,11 +160,12 @@ function setFlows(_config,type,muteLog,forceStart) { activeFlowConfig = newFlowConfig; if (forceStart || started) { return stop(type,diff,muteLog).then(function() { - context.clean(activeFlowConfig); - start(type,diff,muteLog).then(function() { - events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + return context.clean(activeFlowConfig).then(function() { + start(type,diff,muteLog).then(function() { + events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + }); + return flowRevision; }); - return flowRevision; }).catch(function(err) { }) } else { diff --git a/red/runtime/nodes/index.js b/red/runtime/nodes/index.js index be84130ef..cbb78784e 100644 --- a/red/runtime/nodes/index.js +++ b/red/runtime/nodes/index.js @@ -218,5 +218,9 @@ module.exports = { setCredentialSecret: credentials.setKey, clearCredentials: credentials.clear, exportCredentials: credentials.export, - getCredentialKeyType: credentials.getKeyType + getCredentialKeyType: credentials.getKeyType, + + // Contexts + loadContextsPlugin: context.load, + closeContextsPlugin: context.close }; diff --git a/test/red/runtime/index_spec.js b/test/red/runtime/index_spec.js index 4e30c1e8a..c2adfa9c2 100644 --- a/test/red/runtime/index_spec.js +++ b/test/red/runtime/index_spec.js @@ -79,6 +79,7 @@ describe("runtime", function() { var redNodesGetNodeList; var redNodesLoadFlows; var redNodesStartFlows; + var redNodesLoadContextsPlugin; beforeEach(function() { storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();}); @@ -91,6 +92,7 @@ describe("runtime", function() { redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); + redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return when.resolve()}); }); afterEach(function() { storageInit.restore(); @@ -104,6 +106,7 @@ describe("runtime", function() { redNodesCleanModuleList.restore(); redNodesLoadFlows.restore(); redNodesStartFlows.restore(); + redNodesLoadContextsPlugin.restore(); }); it("reports errored/missing modules",function(done) { redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) { @@ -183,7 +186,7 @@ describe("runtime", function() { }); it("reports runtime metrics",function(done) { - var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); + var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} ); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); logMetric.restore(); logMetric = sinon.stub(log,"metric",function() { return true; }); @@ -214,10 +217,18 @@ describe("runtime", function() { }); - it("stops components", function() { - var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); - runtime.stop(); - stopFlows.called.should.be.true(); - stopFlows.restore(); + it("stops components", function(done) { + var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} ); + var closeContextsPlugin = sinon.stub(redNodes,"closeContextsPlugin",function() { return when.resolve();} ); + runtime.stop().then(function(){ + stopFlows.called.should.be.true(); + closeContextsPlugin.called.should.be.true(); + done(); + }).catch(function(err){ + return done(err) + }).finally(function(){ + stopFlows.restore(); + closeContextsPlugin.restore(); + }); }); }); diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js new file mode 100644 index 000000000..fa1332f20 --- /dev/null +++ b/test/red/runtime/nodes/context/index_spec.js @@ -0,0 +1,479 @@ +/** + * 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 Context = require("../../../../../red/runtime/nodes/context/index"); + +describe('context', function() { + describe('local memory',function() { + beforeEach(function() { + Context.init({}); + return 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', 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('should enumerate only context keys when GlobalContext was given', function() { + Context.init({functionGlobalContext: {foo:"bar"}}); + return Context.load().then(function(){ + var context = Context.get("1","flowA"); + var keys = context.global.keys(); + keys.should.have.length(1); + keys[0].should.equal("foo"); + }); + }); + }); + + 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:{} + } + }; + + afterEach(function() { + sandbox.reset(); + return Context.clean({allNodes:{}}).then(function(){ + return Context.close(); + }); + }); + + describe('load modules',function(){ + it('should call open()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return 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 accept special storage name', function(done) { + Context.init({ + contextStorage:{ + "#%&":{module:testPlugin}, + \u3042:{module:testPlugin}, + 1:{module:testPlugin}, + } + }); + Context.load().then(function(){ + var context = Context.get("1","flow"); + var cb = function(){done("An error occurred")} + context.set("sign","sign1","#%&",cb); + context.set("file","file2","\u3042",cb); + context.set("num","num3","1",cb); + stubSet.calledWithExactly("1:flow","sign","sign1",cb).should.be.true(); + stubSet.calledWithExactly("1:flow","file","file2",cb).should.be.true(); + stubSet.calledWithExactly("1:flow","num","num3",cb).should.be.true(); + done(); + }); + }); + 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(); + }); + }); + 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(); + }); + }); + }); + + describe('close modules',function(){ + it('should call close()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.close().then(function(){ + stubClose.called.should.be.true(); + stubClose2.called.should.be.true(); + }); + }); + }); + }); + + 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.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("1:flow",cb).should.be.true(); + 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.calledWithExactly("flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("flow",cb).should.be.true(); + 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.calledWithExactly("global","foo",cb).should.be.true(); + stubKeys.calledWithExactly("global",cb).should.be.true(); + 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.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + 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","defaultt",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.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + 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.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + 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.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("1:flow",cb).should.be.true(); + done(); + }); + }); + it('should 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); + should.fail(null, null, "An error was not thrown using undefined storage for local context"); + }).catch(function(err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + }); + }); + 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); + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + }).catch(function(err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + }); + }); + }); + + describe('delete context',function(){ + it('should not call delete() when external context storage is used', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + Context.get("flowA"); + return Context.delete("flowA").then(function(){ + stubDelete.called.should.be.false(); + stubDelete2.called.should.be.false(); + }); + }); + }); + }); + + describe('clean context',function(){ + it('should call clean()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.clean({allNodes:{}}).then(function(){ + stubClean.calledWithExactly([]).should.be.true(); + stubClean2.calledWithExactly([]).should.be.true(); + }); + }); + }); + }); + }); +}); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js new file mode 100644 index 000000000..a03d5b24a --- /dev/null +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -0,0 +1,378 @@ +/** + * 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")); + +describe('localfilesystem',function() { + var context; + + before(function() { + return fs.remove(resourcesDir); + }); + + beforeEach(function() { + context = LocalFileSystem({dir: resourcesDir}); + return context.open(); + }); + + afterEach(function() { + return context.clean([]).then(function(){ + return context.close().then(function(){ + return fs.remove(resourcesDir); + }); + }); + }); + + describe('#get/set',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.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 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(); + }); + }); + }); + }); + }); + }); + + describe('#keys',function() { + 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(); + }); + }); + }); + }); + }); + }); + }); + }); + + describe('#delete',function() { + 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(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + describe('#clean',function() { + it('should clean unnecessary 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.clean([]).then(function(){ + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + it('should not clean active 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.clean(["nodeX"]).then(function(){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js new file mode 100644 index 000000000..090c46b41 --- /dev/null +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -0,0 +1,179 @@ +/** + * 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() { + 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"); + }); + }); + + describe('#keys',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 only context keys when GlobalContext was given', function() { + var keys = context.keys("global"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + var data = { + foo: "bar" + } + context.setGlobalContext(data); + keys = context.keys("global"); + keys.should.have.length(1); + keys[0].should.equal("foo"); + }); + }); + + 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")); + }); + }); + }); + + describe('#setGlobalContext',function() { + it('should initialize global context with argument', function() { + var keys = context.keys("global"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + var data = { + foo: "bar" + } + context.setGlobalContext(data); + context.get("global","foo").should.equal("bar"); + }); + }); +}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context_spec.js b/test/red/runtime/nodes/context_spec.js deleted file mode 100644 index 3e7c03de4..000000000 --- a/test/red/runtime/nodes/context_spec.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var should = require("should"); -var sinon = require('sinon'); -var Context = require("../../../../red/runtime/nodes/context"); - -describe('context', function() { - beforeEach(function() { - Context.init({}); - }); - afterEach(function() { - Context.clean({allNodes:{}}); - }); - 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.eql("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.eql("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.eql("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.eql("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.eql("test"); - context2.flow.get("foo").should.eql("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.eql("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.eql("test"); - context2.global.get("foo").should.eql("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.eql("abc"); - - Context.delete("1","flowA"); - context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); - }) - - it('enumerates context keys', 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.eql("foo"); - - context.set("abc.def","bar"); - keys = context.keys(); - keys.should.have.length(2); - keys[1].should.eql("abc"); - - - - - }) - -});