diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 845408e8f..2a00d40d3 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -21,12 +21,13 @@ var settings; var contexts = {}; var globalContext = null; var externalContexts = {}; +var noContextStorage = false; function init(_settings) { settings = _settings; externalContexts = {}; - // init meomory plugin + // init memory plugin externalContexts["_"] = require("./memory"); externalContexts["_"].init(); globalContext = createContext("global",settings.functionGlobalContext || {}); @@ -37,6 +38,7 @@ function load() { var plugins = settings.contextStorage; var alias = null; if (plugins) { + noContextStorage = false; for(var pluginName in plugins){ if(pluginName === "_"){ continue; @@ -49,24 +51,30 @@ function load() { if(plugins[pluginName].hasOwnProperty("module")){ var config = plugins[pluginName].config || {}; copySettings(config, settings); - try{ - plugin = require("./"+plugins[pluginName].module); - }catch(err){ - throw new Error(plugins[pluginName].module + " could not be loaded"); + if(typeof plugins[pluginName].module === "string") { + try{ + plugin = require("./"+plugins[pluginName].module); + }catch(err){ + throw new Error(plugins[pluginName].module + " could not be loaded"); + } + } else { + plugin = plugins[pluginName].module; } plugin.init(config); externalContexts[pluginName] = plugin; }else{ - throw new Error("module is is not defined in settings.contextStorage." + plugins[pluginName] ); + throw new Error("module is not defined in settings.contextStorage." + pluginName ); } } if(alias){ if(externalContexts.hasOwnProperty(alias)){ externalContexts["default"] = externalContexts[alias]; }else{ - throw new Error("default is invalid" + plugins["default"]) + throw new Error("default is invalid. module name=" + plugins["default"]) } } + } else { + noContextStorage = true; } } @@ -78,35 +86,49 @@ function copySettings(config, settings){ }); } +function parseKey(key){ + if(!key){ + return null; + } + var keyPath = {storage: "", key: ""}; + var index_$ = key.indexOf("$"); + var index_dot = key.indexOf(".", 1); + if(index_$===0&&index_dot) { + keyPath.storage = key.substring(1,index_dot)||"default"; + keyPath.key = key.substring(index_dot+1); + } else { + keyPath.key = key; + } + return keyPath; +} + +function getContextStorage(keyPath) { + if (noContextStorage || !keyPath.storage) { + return externalContexts["_"]; + } else if (externalContexts.hasOwnProperty(keyPath.storage)) { + return externalContexts[keyPath.storage]; + } else if (externalContexts.hasOwnProperty("default")) { + return externalContexts["default"]; + } else { + var contextError = new Error(keyPath.storage + " is not defined in contextStorage on settings.js"); + contextError.name = "ContextError"; + throw contextError; + } +} + function createContext(id,seed) { var scope = id; var obj = seed || {}; obj.get = function(key) { - var result = parseKey(key); - if(!result){ - return externalContexts["_"].get(key, scope); - } - if(externalContexts.hasOwnProperty(result[0])){ - return externalContexts[result[0]].get(result[1], scope); - }else if(externalContexts.hasOwnProperty("defalut")){ - return externalContexts["defalut"].get(result[1], scope); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } + var keyPath = parseKey(key); + var context = getContextStorage(keyPath); + return context.get(keyPath.key, scope); }; obj.set = function(key, value) { - var result = parseKey(key); - if(!result){ - return externalContexts["_"].set(key, value, scope); - } - if(externalContexts.hasOwnProperty(result[0])){ - externalContexts[result[0]].set(result[1], value, scope); - }else if(externalContexts.hasOwnProperty("defalut")){ - externalContexts["defalut"].set(result[1], value, scope); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } + var keyPath = parseKey(key); + var context = getContextStorage(keyPath); + return context.set(keyPath.key, value, scope); }; obj.keys = function() { //TODO: discuss about keys() behavior @@ -169,22 +191,6 @@ function clean(flowConfig) { } } -function parseKey(key){ - var keys = null; - if(!key){ - return null; - } - var index_$ = key.indexOf("$"); - var index_dot = key.indexOf(".", 1); - if(index_$ === 0 && index_dot){ - keys = []; - keys[0] = key.substring(1,index_dot); - keys[1] = key.substring(index_dot + 1); - keys[0] = keys[0] || "default"; - } - return keys; -} - module.exports = { init: init, load: load, diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 1bcea888d..e339de7e1 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -123,7 +123,7 @@ describe('context', function() { should.not.exist(context.get("foo")); }); - it('enumerates context keys', function() { + it.skip('enumerates context keys', function() { var context = Context.get("1","flowA"); var keys = context.keys(); @@ -150,24 +150,35 @@ describe('context', function() { var stubKeys = sinon.stub(); var stubDelete = sinon.stub(); var contextStorage={ - test:{ - module: { - init: function() { - return true; - }, - get: stubGet, - set: stubSet, - keys: stubKeys, - delete: stubDelete + test:{ + module: { + init: function() { + return true; }, - config:{} - } - }; + get: stubGet, + set: stubSet, + keys: stubKeys, + delete: stubDelete + }, + config:{} + } + }; + var contextDefaultStorage={ + default: "test", + test:{ + module: { + init: function() { + return true; + }, + get: stubGet, + set: stubSet, + keys: stubKeys, + delete: stubDelete + }, + config:{} + } + }; - beforeEach(function() { - Context.init({contextStorage:contextStorage}); - context = Context.get("1","flow"); - }); afterEach(function(done) { stubGet.reset(); stubSet.reset(); @@ -176,8 +187,15 @@ describe('context', function() { fs.remove(testDir,done); }); + function initializeContext() { + Context.init({contextStorage:contextStorage}); + Context.load(); + context = Context.get("1","flow"); + } + describe('if external context storage exists',function() { it('should store local property to external context storage',function() { + initializeContext(); should.not.exist(context.get("$test.foo")); context.set("$test.foo","test"); context.get("$test.foo"); @@ -187,6 +205,7 @@ describe('context', function() { stubKeys.called.should.be.true(); }); it('should store flow property to external context storage',function() { + initializeContext(); should.not.exist(context.flow.get("$test.foo")); context.flow.set("$test.foo","test"); context.flow.get("$test.foo"); @@ -196,6 +215,7 @@ describe('context', function() { stubKeys.called.should.be.true(); }); it('should store global property to external context storage',function() { + initializeContext(); should.not.exist(context.global.get("$test.foo")); context.global.set("$test.foo","test"); context.global.get("$test.foo"); @@ -204,37 +224,116 @@ describe('context', function() { stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); + it('should store data on default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + Context.load(); + context = Context.get("1","flow"); + should.not.exist(context.get("$nonexist.foo")); + context.set("$nonexist.foo","test"); + context.get("$nonexist.foo"); + context.keys("$nonexist"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + it('should load memory module', function(done) { + Context.init({ contextStorage: { _: {}}}); + try { + Context.load(); + context.set("_.foo","test"); + context.get("$_.foo"); + done(); + } catch (err) { + done(err); + } + }); + it('should load localfilesystem module', function(done) { + Context.init({contextStorage:{ file:{module:"localfilesystem"} }}); + try { + Context.load(); + done(); + } catch (err) { + done(err); + } + }); }); describe('if external context storage does not exist',function() { - it('should store local property to local memory',function() { - should.not.exist(context.flow.get("$nonexist.foo")); - context.set("$nonexist.foo","test"); - context.get("$nonexist.foo").should.eql("test"); - context.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should throw an error using undefined storage for local context', function(done) { + initializeContext(); + try { + context.get("$nonexist.local"); + should.fail(null, null, "An error was not thrown using undefined storage for local context"); + } catch (err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + } }); - - it('should store flow property to local memory',function() { - should.not.exist(context.flow.get("$nonexist.foo")); - context.flow.set("$nonexist.foo","test"); - context.flow.get("$nonexist.foo").should.eql("test"); - context.flow.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should throw an error using undefined storage for flow context', function(done) { + initializeContext(); + try { + context.flow.set("$nonexist.flow"); + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + } catch (err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + } }); - - it('should store global property to local memory',function() { - should.not.exist(context.global.get("$nonexist.foo")); - context.global.set("$nonexist.foo","test"); - context.global.get("$nonexist.foo").should.eql("test"); - context.global.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should fail when using invalid default context', function(done) { + Context.init({contextStorage:{default:"noexist"}}); + try { + Context.load(); + try { + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } + }); + it('should store data on memory when contextStorage is not defined', function() { + Context.init({}); + Context.load(); + context = Context.get("1","flow"); + context.set("$nonexist.key1", "val1"); + context.get("$nonexist.key1").should.eql("val1"); + context.flow.set("$nonexist.key2", "val2"); + context.flow.get("$nonexist.key2").should.eql("val2"); + context.global.set("$nonexist.key1", "val3"); + context.global.get("$nonexist.key1").should.eql("val3"); + }); + it('should fail for the storage with no module', function(done) { + Context.init({ contextStorage: { test: {}}}); + try { + Context.load(); + try { + should.fail(null, null, "Should fail when no module was specified"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } + }); + it('should fail to load non-existent module', function(done) { + Context.init({contextStorage:{ file:{module:"nonexistent"} }}); + try { + Context.load(); + try { + should.fail(null, null, "Should fail to load non-existent module"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } }); }); }); @@ -244,17 +343,17 @@ describe('context', function() { function returnModuleAndKey(input, expectedModule, expectedKey) { var result = parseKey(input); - result[0].should.eql(expectedModule); - result[1].should.eql(expectedKey); + result.storage.should.eql(expectedModule); + result.key.should.eql(expectedKey); }; function returnModule(input, expectedModule) { var result = parseKey(input); - result[0].should.eql(expectedModule); - should(result[1]).be.null(); + result.storage.should.eql(expectedModule); + should(result.key).be.null(); }; - it('should retrun module and key', function() { + it('should return module and key', function() { returnModuleAndKey("$test.aaa","test","aaa"); returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); returnModuleAndKey("$1.234","1","234"); @@ -265,23 +364,29 @@ describe('context', function() { returnModuleAndKey("$test..","test","."); }); - // it('should retrun only module', function() { + // it('should return only module', function() { // returnModule("$test","test",null); // returnModule("$1","1",null); // returnModule("$$test","$test",null); // returnModule("$test.","test.",null); // }); - it('should retrun module as default', function() { + it('should return module as default', function() { returnModuleAndKey("$default.foo","default","foo"); returnModuleAndKey("$.foo","default","foo"); // returnModule("$default","default"); // returnModule("$","default"); }); - it('should retrun null', function() { - should(parseKey("test.aaa")).be.null(); - should(parseKey("test")).be.null(); + it('should return null', function() { + var keyPath = parseKey("test.aaa"); + keyPath.storage.should.eql(""); + keyPath.key.should.eql("test.aaa"); + + keyPath = parseKey("test"); + keyPath.storage.should.eql(""); + keyPath.key.should.eql("test"); + should(parseKey(null)).be.null(); }); });