From 543519d055955f01c0e2315879beeb6ce5cd1204 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Wed, 9 Jan 2019 18:13:33 +0900 Subject: [PATCH 1/3] Add test cases to ensure context API routes are correctly mounted. --- .../editor-api/lib/admin/index_spec.js | 223 ++++++++++++++---- 1 file changed, 180 insertions(+), 43 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/admin/index_spec.js b/test/unit/@node-red/editor-api/lib/admin/index_spec.js index c20df8d1b..ead5f274a 100644 --- a/test/unit/@node-red/editor-api/lib/admin/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/index_spec.js @@ -26,6 +26,7 @@ var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth"); var nodes = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/nodes"); var flows = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flows"); var flow = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/flow"); +var context = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/context"); /** * Ensure all API routes are correctly mounted, with the expected permissions checks @@ -34,8 +35,8 @@ describe("api/admin/index", function() { describe("Ensure all API routes are correctly mounted, with the expected permissions checks", function() { var app; var mockList = [ - flows,flow,nodes - ] + flows,flow,nodes,context + ]; var permissionChecks = {}; var lastRequest; var stubApp = function(req,res,next) { @@ -50,7 +51,7 @@ describe("api/admin/index", function() { return function(req,res,next) { permissionChecks[permission] = (permissionChecks[permission]||0)+1; next(); - } + }; }); sinon.stub(flows,"get",stubApp); @@ -70,6 +71,9 @@ describe("api/admin/index", function() { sinon.stub(nodes,"putSet",stubApp); sinon.stub(nodes,"getModuleCatalog",stubApp); sinon.stub(nodes,"getModuleCatalogs",stubApp); + + sinon.stub(context,"get",stubApp); + sinon.stub(context,"delete",stubApp); }); after(function() { mockList.forEach(function(m) { @@ -92,15 +96,19 @@ describe("api/admin/index", function() { nodes.putSet.restore(); nodes.getModuleCatalog.restore(); nodes.getModuleCatalogs.restore(); + context.get.restore(); + context.delete.restore(); }); before(function() { app = adminApi.init({}); }); + beforeEach(function() { permissionChecks = {}; - }) + }); + it('GET /flows', function(done) { request(app).get("/flows").expect(200).end(function(err,res) { if (err) { @@ -108,8 +116,9 @@ describe("api/admin/index", function() { } permissionChecks.should.have.property('flows.read',1); done(); - }) + }); }); + it('POST /flows', function(done) { request(app).post("/flows").expect(200).end(function(err,res) { if (err) { @@ -117,7 +126,7 @@ describe("api/admin/index", function() { } permissionChecks.should.have.property('flows.write',1); done(); - }) + }); }); it('GET /flow/1234', function(done) { @@ -126,10 +135,11 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('flows.read',1); - lastRequest.params.should.have.property('id','1234') + lastRequest.params.should.have.property('id','1234'); done(); - }) + }); }); + it('POST /flow', function(done) { request(app).post("/flow").expect(200).end(function(err,res) { if (err) { @@ -137,27 +147,29 @@ describe("api/admin/index", function() { } permissionChecks.should.have.property('flows.write',1); done(); - }) + }); }); + it('DELETE /flow/1234', function(done) { request(app).del("/flow/1234").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('flows.write',1); - lastRequest.params.should.have.property('id','1234') + lastRequest.params.should.have.property('id','1234'); done(); - }) + }); }); + it('PUT /flow/1234', function(done) { request(app).put("/flow/1234").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('flows.write',1); - lastRequest.params.should.have.property('id','1234') + lastRequest.params.should.have.property('id','1234'); done(); - }) + }); }); it('GET /nodes', function(done) { @@ -167,8 +179,9 @@ describe("api/admin/index", function() { } permissionChecks.should.have.property('nodes.read',1); done(); - }) + }); }); + it('POST /nodes', function(done) { request(app).post("/nodes").expect(200).end(function(err,res) { if (err) { @@ -176,27 +189,29 @@ describe("api/admin/index", function() { } permissionChecks.should.have.property('nodes.write',1); done(); - }) + }); }); + it('GET /nodes/module', function(done) { request(app).get("/nodes/module").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.read',1); - lastRequest.params.should.have.property(0,'module') + lastRequest.params.should.have.property(0,'module'); done(); - }) + }); }); + it('GET /nodes/@scope/module', function(done) { request(app).get("/nodes/@scope/module").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.read',1); - lastRequest.params.should.have.property(0,'@scope/module') + lastRequest.params.should.have.property(0,'@scope/module'); done(); - }) + }); }); it('PUT /nodes/module', function(done) { @@ -205,19 +220,20 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'module') + lastRequest.params.should.have.property(0,'module'); done(); - }) + }); }); + it('PUT /nodes/@scope/module', function(done) { request(app).put("/nodes/@scope/module").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'@scope/module') + lastRequest.params.should.have.property(0,'@scope/module'); done(); - }) + }); }); it('DELETE /nodes/module', function(done) { @@ -226,19 +242,20 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'module') + lastRequest.params.should.have.property(0,'module'); done(); - }) + }); }); + it('DELETE /nodes/@scope/module', function(done) { request(app).del("/nodes/@scope/module").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'@scope/module') + lastRequest.params.should.have.property(0,'@scope/module'); done(); - }) + }); }); it('GET /nodes/module/set', function(done) { @@ -247,21 +264,22 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('nodes.read',1); - lastRequest.params.should.have.property(0,'module') - lastRequest.params.should.have.property(2,'set') + lastRequest.params.should.have.property(0,'module'); + lastRequest.params.should.have.property(2,'set'); done(); - }) + }); }); + it('GET /nodes/@scope/module/set', function(done) { request(app).get("/nodes/@scope/module/set").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.read',1); - lastRequest.params.should.have.property(0,'@scope/module') - lastRequest.params.should.have.property(2,'set') + lastRequest.params.should.have.property(0,'@scope/module'); + lastRequest.params.should.have.property(2,'set'); done(); - }) + }); }); it('PUT /nodes/module/set', function(done) { @@ -270,21 +288,22 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'module') - lastRequest.params.should.have.property(2,'set') + lastRequest.params.should.have.property(0,'module'); + lastRequest.params.should.have.property(2,'set'); done(); - }) + }); }); + it('PUT /nodes/@scope/module/set', function(done) { request(app).put("/nodes/@scope/module/set").expect(200).end(function(err,res) { if (err) { return done(err); } permissionChecks.should.have.property('nodes.write',1); - lastRequest.params.should.have.property(0,'@scope/module') - lastRequest.params.should.have.property(2,'set') + lastRequest.params.should.have.property(0,'@scope/module'); + lastRequest.params.should.have.property(2,'set'); done(); - }) + }); }); it('GET /nodes/messages', function(done) { @@ -293,10 +312,10 @@ describe("api/admin/index", function() { return done(err); } permissionChecks.should.have.property('nodes.read',1); - done(); - }) + }); }); + it('GET /nodes/module/set/messages', function(done) { request(app).get("/nodes/module/set/messages").expect(200).end(function(err,res) { if (err) { @@ -305,8 +324,9 @@ describe("api/admin/index", function() { permissionChecks.should.have.property('nodes.read',1); lastRequest.params.should.have.property(0,'module/set'); done(); - }) + }); }); + it('GET /nodes/@scope/module/set/messages', function(done) { request(app).get("/nodes/@scope/module/set/messages").expect(200).end(function(err,res) { if (err) { @@ -315,7 +335,124 @@ describe("api/admin/index", function() { permissionChecks.should.have.property('nodes.read',1); lastRequest.params.should.have.property(0,'@scope/module/set'); done(); - }) + }); + }); + + it('GET /context/global', function(done) { + request(app).get("/context/global").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','global'); + done(); + }); + }); + + it('GET /context/global/key?store=memory', function(done) { + request(app).get("/context/global/key?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','global'); + lastRequest.params.should.have.property(0,'key'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); + }); + + it('GET /context/flow/1234', function(done) { + request(app).get("/context/flow/1234").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','flow'); + lastRequest.params.should.have.property('id','1234'); + done(); + }); + }); + + it('GET /context/flow/1234/key?store=memory', function(done) { + request(app).get("/context/flow/1234/key?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','flow'); + lastRequest.params.should.have.property('id','1234'); + lastRequest.params.should.have.property(0,'key'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); + }); + + it('GET /context/node/5678', function(done) { + request(app).get("/context/node/5678").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','node'); + lastRequest.params.should.have.property('id','5678'); + done(); + }); + }); + + it('GET /context/node/5678/foo?store=memory', function(done) { + request(app).get("/context/node/5678/foo?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.read',1); + lastRequest.params.should.have.property('scope','node'); + lastRequest.params.should.have.property('id','5678'); + lastRequest.params.should.have.property(0,'foo'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); + }); + + it('DELETE /context/global/key?store=memory', function(done) { + request(app).del("/context/global/key?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.write',1); + lastRequest.params.should.have.property('scope','global'); + lastRequest.params.should.have.property(0,'key'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); + }); + + it('DELETE /context/flow/1234/key?store=memory', function(done) { + request(app).del("/context/flow/1234/key?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.write',1); + lastRequest.params.should.have.property('scope','flow'); + lastRequest.params.should.have.property('id','1234'); + lastRequest.params.should.have.property(0,'key'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); + }); + + it('DELETE /context/node/5678/foo?store=memory', function(done) { + request(app).del("/context/node/5678/foo?store=memory").expect(200).end(function(err,res) { + if (err) { + return done(err); + } + permissionChecks.should.have.property('context.write',1); + lastRequest.params.should.have.property('scope','node'); + lastRequest.params.should.have.property('id','5678'); + lastRequest.params.should.have.property(0,'foo'); + lastRequest.query.should.have.property('store','memory'); + done(); + }); }); }); }); From f98f4085bfde0c6114cb1c873233b3627c38b229 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 18 Jan 2019 18:55:54 +0900 Subject: [PATCH 2/3] Add test cases for context admin API --- .../editor-api/lib/admin/context_spec.js | 359 +++++++++--------- 1 file changed, 176 insertions(+), 183 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/admin/context_spec.js b/test/unit/@node-red/editor-api/lib/admin/context_spec.js index 979c3e6eb..a9dc0f70d 100644 --- a/test/unit/@node-red/editor-api/lib/admin/context_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/context_spec.js @@ -19,22 +19,15 @@ var request = require('supertest'); var express = require('express'); var bodyParser = require('body-parser'); var sinon = require('sinon'); -var when = require('when'); var NR_TEST_UTILS = require("nr-test-utils"); var context = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/context"); -// var Context = require("../../../../red/runtime/nodes/context"); -// var Util = require("../../../../red/runtime/util"); -describe("api/admin/context", function() { - it.skip("NEEDS TESTS WRITING",function() {}); -}); -/* +describe("api/admin/context", function () { var app = undefined; - before(function (done) { - var node_context = undefined; + before(function () { app = express(); app.use(bodyParser.json()); app.get("/context/:scope(global)", context.get); @@ -42,196 +35,196 @@ describe("api/admin/context", function() { app.get("/context/:scope(node|flow)/:id", context.get); app.get("/context/:scope(node|flow)/:id/*", context.get); - context.init({ - settings: { - }, - log:{warn:function(){},_:function(){},audit:function(){}}, - nodes: { - listContextStores: Context.listStores, - getContext: Context.get, - getNode: function(id) { - if (id === 'NID') { - return { - id: 'NID', - context: function () { - return node_context; - } - }; - } - return null; - } - }, - util: Util - }); - - Context.init({ - contextStorage: { - memory0: { - module: "memory" - }, - memory1: { - module: "memory" - } - } - }); - Context.load().then(function () { - var ctx = Context.get("NID", "FID"); - node_context = ctx; - ctx.set("foo", "n_v00", "memory0"); - ctx.set("bar", "n_v01", "memory0"); - ctx.set("baz", "n_v10", "memory1"); - ctx.set("bar", "n_v11", "memory1"); - ctx.flow.set("foo", "f_v00", "memory0"); - ctx.flow.set("bar", "f_v01", "memory0"); - ctx.flow.set("baz", "f_v10", "memory1"); - ctx.flow.set("bar", "f_v11", "memory1"); - ctx.global.set("foo", "g_v00", "memory0"); - ctx.global.set("bar", "g_v01", "memory0"); - ctx.global.set("baz", "g_v10", "memory1"); - ctx.global.set("bar", "g_v11", "memory1"); - done(); - }); - + app.delete("/context/:scope(global)/*", context.delete); + app.delete("/context/:scope(node|flow)/:id/*", context.delete); }); - after(function () { - Context.clean({allNodes:{}}); - Context.close(); - }); + describe("get", function () { + var gContext = { + default: { abc: { msg: '111', format: 'number' } }, + file: { abc: { msg: '222', format: 'number' } } + }; + var fContext = { + default: { bool: { msg: 'true', format: 'boolean' } }, + file: { string: { msg: 'aaaa', format: 'string[7]' } } + }; + var nContext = { msg: "1", format: "number" }; + var stub = sinon.stub(); - function check_mem(body, mem, name, val) { - var mem0 = body[mem]; - mem0.should.have.property(name); - mem0[name].should.deepEqual(val); - } - - function check_scope(scope, prefix, id) { - describe('# '+scope, function () { - var xid = id ? ("/"+id) : ""; - - it('should return '+scope+' contexts', function (done) { - request(app) - .get('/context/'+scope+xid) - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - var body = res.body; - body.should.have.key('memory0', 'memory1'); - check_mem(body, 'memory0', - 'foo', {msg:prefix+'_v00', format:'string[5]'}); - check_mem(body, 'memory0', - 'bar', {msg:prefix+'_v01', format:'string[5]'}); - check_mem(body, 'memory1', - 'baz', {msg:prefix+'_v10', format:'string[5]'}); - check_mem(body, 'memory1', - 'bar', {msg:prefix+'_v11', format:'string[5]'}); - done(); - }); - }); - - it('should return a value from default '+scope+' context', function (done) { - request(app) - .get('/context/'+scope+xid+'/foo') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - var body = res.body; - body.should.deepEqual({msg: prefix+'_v00', format: 'string[5]'}); - done(); - }); - }); - - it('should return a value from specified '+scope+' context', function (done) { - request(app) - .get('/context/'+scope+xid+'/bar?store=memory1') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - var body = res.body; - body.should.deepEqual({msg: prefix+'_v11', format: 'string[5]', store: 'memory1'}); - done(); - }); - }); - - it('should return specified '+scope+' store', function (done) { - request(app) - .get('/context/'+scope+xid+'?store=memory1') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - var body = res.body; - body.should.deepEqual({ - memory1: { - baz: { msg: prefix+'_v10', format: 'string[5]' }, - bar: { msg: prefix+'_v11', format: 'string[5]' } - } - }); - done(); - }); - }); - - it('should return undefined for unknown key of default '+scope+' store', function (done) { - request(app) - .get('/context/'+scope+xid+'/unknown') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - var body = res.body; - body.should.deepEqual({msg:'(undefined)', format:'undefined'}); - done(); - - }); - }); - - it('should cause error for unknown '+scope+' store', function (done) { - request(app) - .get('/context/'+scope+xid+'?store=unknown') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, res) { - if (err) { - return done(); - } - done("unexpected"); - }); + before(function () { + context.init({ + context: { + getValue: stub + } }); }); - } - check_scope("global", "g", undefined); - check_scope("node", "n", "NID"); - check_scope("flow", "f", "FID"); + afterEach(function () { + stub.reset(); + }); - describe("# errors", function () { - it('should cause error for unknown scope', function (done) { + it('should call context.getValue to get global contexts', function (done) { + stub.returns(Promise.resolve(gContext)); request(app) - .get('/context/scope') + .get('/context/global') .set('Accept', 'application/json') .expect(200) .end(function (err, res) { if (err) { - return done(); + return done(err); } - done("unexpected"); + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'global'); + stub.args[0][0].should.have.property('id', undefined); + stub.args[0][0].should.have.property('key', undefined); + stub.args[0][0].should.have.property('store', undefined); + var body = res.body; + body.should.eql(gContext); + done(); }); }); + it('should call context.getValue to get flow contexts', function (done) { + stub.returns(Promise.resolve(fContext)); + request(app) + .get('/context/flow/1234/') + .set('Accept', 'application/json') + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'flow'); + stub.args[0][0].should.have.property('id', '1234'); + stub.args[0][0].should.have.property('key', undefined); + stub.args[0][0].should.have.property('store', undefined); + var body = res.body; + body.should.eql(fContext); + done(); + }); + }); + + it('should call context.getValue to get a node context', function (done) { + stub.returns(Promise.resolve(nContext)); + request(app) + .get('/context/node/5678/foo?store=file') + .set('Accept', 'application/json') + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'node'); + stub.args[0][0].should.have.property('id', '5678'); + stub.args[0][0].should.have.property('key', 'foo'); + stub.args[0][0].should.have.property('store', 'file'); + var body = res.body; + body.should.eql(nContext); + done(); + }); + }); + + it('should handle error which context.getValue causes', function (done) { + stub.returns(Promise.reject('error')); + request(app) + .get('/context/global') + .set('Accept', 'application/json') + .expect(400) + .end(function (err, res) { + if (err) { + return done(err); + } + res.body.should.has.a.property('code', 'unexpected_error'); + res.body.should.has.a.property('message', 'error'); + done(); + }); + }); }); + describe("delete", function () { + var stub = sinon.stub(); + + before(function () { + context.init({ + context: { + delete: stub + } + }); + }); + + afterEach(function () { + stub.reset(); + }); + + it('should call context.delete to delete a global context', function (done) { + stub.returns(Promise.resolve()); + request(app) + .delete('/context/global/abc?store=default') + .expect(204) + .end(function (err, res) { + if (err) { + return done(err); + } + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'global'); + stub.args[0][0].should.have.property('id', undefined); + stub.args[0][0].should.have.property('key', 'abc'); + stub.args[0][0].should.have.property('store', 'default'); + done(); + }); + }); + + it('should call context.delete to delete a flow context', function (done) { + stub.returns(Promise.resolve()); + request(app) + .delete('/context/flow/1234/abc?store=file') + .expect(204) + .end(function (err, res) { + if (err) { + return done(err); + } + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'flow'); + stub.args[0][0].should.have.property('id', '1234'); + stub.args[0][0].should.have.property('key', 'abc'); + stub.args[0][0].should.have.property('store', 'file'); + done(); + }); + }); + + it('should call context.delete to delete a node context', function (done) { + stub.returns(Promise.resolve()); + request(app) + .delete('/context/node/5678/foo?store=file') + .expect(204) + .end(function (err, res) { + if (err) { + return done(err); + } + stub.args[0][0].should.have.property('user', undefined); + stub.args[0][0].should.have.property('scope', 'node'); + stub.args[0][0].should.have.property('id', '5678'); + stub.args[0][0].should.have.property('key', 'foo'); + stub.args[0][0].should.have.property('store', 'file'); + done(); + }); + }); + + it('should handle error which context.delete causes', function (done) { + stub.returns(Promise.reject('error')); + request(app) + .delete('/context/global/abc?store=default') + .expect(400) + .end(function (err, res) { + if (err) { + return done(err); + } + res.body.should.has.a.property('code', 'unexpected_error'); + res.body.should.has.a.property('message', 'error'); + done(); + }); + }); + }); }); -*/ From f7c87e26db99a5a210895f766b8b5f7230afab95 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 18 Jan 2019 19:53:15 +0900 Subject: [PATCH 3/3] Add test cases for context runtime API --- .../@node-red/runtime/lib/api/context_spec.js | 260 ++++++++++++++---- 1 file changed, 200 insertions(+), 60 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/api/context_spec.js b/test/unit/@node-red/runtime/lib/api/context_spec.js index db604f2a9..bf23e9cc6 100644 --- a/test/unit/@node-red/runtime/lib/api/context_spec.js +++ b/test/unit/@node-red/runtime/lib/api/context_spec.js @@ -19,7 +19,7 @@ var should = require("should"); var sinon = require("sinon"); var NR_TEST_UTILS = require("nr-test-utils"); -var context = NR_TEST_UTILS.require("@node-red/runtime/lib/api/context") +var context = NR_TEST_UTILS.require("@node-red/runtime/lib/api/context"); var mockLog = () => ({ log: sinon.stub(), @@ -29,8 +29,8 @@ var mockLog = () => ({ info: sinon.stub(), metric: sinon.stub(), audit: sinon.stub(), - _: function() { return "abc"} -}) + _: function() { return "abc";} +}); var mockContext = function(contents) { return { @@ -41,51 +41,65 @@ var mockContext = function(contents) { callback(null,undefined); } }, - keys: function(store,callback) { + set: function (key, value, store, callback) { if (contents.hasOwnProperty(store)) { - callback(null,Object.keys(contents[store])); + if (!value) { + delete contents[store][key]; + callback(null); + } + } else { + callback("err store"); + } + }, + keys: function (store, callback) { + if (contents.hasOwnProperty(store)) { + callback(null, Object.keys(contents[store])); } else { callback("err store"); } } - } -} + }; +}; + describe("runtime-api/context", function() { - describe("getValue", function() { - var contexts = { - global: mockContext({ default: {abc:111}, file: {abc:222}}), - flow1: mockContext({ default: {abc:333}, file: {abc:444}}) - } - var nodeContext = mockContext({ default: {abc:555}, file: {abc:666}}) + var globalContext, flowContext, nodeContext, contexts; - beforeEach(function() { - context.init({ - nodes: { - listContextStores: function() { - return { default: 'default', stores: [ 'default', 'file' ] } - }, - getContext: function(id) { - return contexts[id] - }, - getNode: function(id) { - if (id === 'known') { - return { - context: function() { return nodeContext } - } - } else { - return null; - } - } + beforeEach(function() { + globalContext = { default: { abc: 111 }, file: { abc: 222 } }; + flowContext = { default: { abc: 333 }, file: { abc: 444 } }; + nodeContext = { default: { abc: 555 }, file: { abc: 666 } }; + contexts = { + global: mockContext(globalContext), + flow1: mockContext(flowContext) + }; + context.init({ + nodes: { + listContextStores: function() { + return { default: 'default', stores: [ 'default', 'file' ] }; }, - settings: { - functionGlobalContext: { - fgc:1234 - } + getContext: function(id) { + return contexts[id]; }, - log: mockLog() - }) + getNode: function(id) { + if (id === 'known') { + return { + context: function() { return mockContext(nodeContext); } + }; + } else { + return null; + } + } + }, + settings: { + functionGlobalContext: { + fgc:1234 + } + }, + log: mockLog() }); + }); + describe("getValue", function() { it('gets global value of default store', function() { return context.getValue({ scope: 'global', @@ -95,8 +109,9 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','111'); result.should.have.property('format','number'); - }) - }) + }); + }); + it('gets global value of specified store', function() { return context.getValue({ scope: 'global', @@ -106,8 +121,9 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','222'); result.should.have.property('format','number'); - }) - }) + }); + }); + it('gets flow value of default store', function() { return context.getValue({ scope: 'flow', @@ -117,8 +133,9 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','333'); result.should.have.property('format','number'); - }) - }) + }); + }); + it('gets flow value of specified store', function() { return context.getValue({ scope: 'flow', @@ -128,8 +145,9 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','444'); result.should.have.property('format','number'); - }) - }) + }); + }); + it('gets node value of default store', function() { return context.getValue({ scope: 'node', @@ -139,8 +157,9 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','555'); result.should.have.property('format','number'); - }) - }) + }); + }); + it('gets node value of specified store', function() { return context.getValue({ scope: 'node', @@ -150,8 +169,8 @@ describe("runtime-api/context", function() { }).then(function(result) { result.should.have.property('msg','666'); result.should.have.property('format','number'); - }) - }) + }); + }); it('404s for unknown store', function(done) { context.getValue({ @@ -162,12 +181,11 @@ describe("runtime-api/context", function() { }).then(function(result) { done("getValue for unknown store should not resolve"); }).catch(function(err) { - err.should.have.property('code','not_found') + err.should.have.property('code','not_found'); err.should.have.property('status',404); done(); - }) - }) - + }); + }); it('gets all global value properties', function() { return context.getValue({ @@ -180,8 +198,9 @@ describe("runtime-api/context", function() { default: { abc: { msg: '111', format: 'number' } }, file: { abc: { msg: '222', format: 'number' } } }); - }) - }) + }); + }); + it('gets all flow value properties', function() { return context.getValue({ scope: 'flow', @@ -193,8 +212,9 @@ describe("runtime-api/context", function() { default: { abc: { msg: '333', format: 'number' } }, file: { abc: { msg: '444', format: 'number' } } }); - }) - }) + }); + }); + it('gets all node value properties', function() { return context.getValue({ scope: 'node', @@ -206,8 +226,128 @@ describe("runtime-api/context", function() { default: { abc: { msg: '555', format: 'number' } }, file: { abc: { msg: '666', format: 'number' } } }); - }) - }) + }); + }); - }) + it('gets empty object when specified context doesn\'t exist', function() { + return context.getValue({ + scope: 'node', + id: 'non-existent', + store: 'file', + key: 'abc' + }).then(function(result) { + result.should.be.an.Object(); + result.should.be.empty(); + }); + }); + }); + + describe("delete", function () { + it('deletes global value of default store', function () { + return context.delete({ + scope: 'global', + id: undefined, + store: undefined, // use default + key: 'abc' + }).then(function () { + globalContext.should.eql({ + default: {}, file: { abc: 222 } + }); + }); + }); + + it('deletes global value of specified store', function () { + return context.delete({ + scope: 'global', + id: undefined, + store: 'file', + key: 'abc' + }).then(function () { + globalContext.should.eql({ + default: { abc: 111 }, file: {} + }); + }); + }); + + it('deletes flow value of default store', function () { + return context.delete({ + scope: 'flow', + id: 'flow1', + store: undefined, // use default + key: 'abc' + }).then(function () { + flowContext.should.eql({ + default: {}, file: { abc: 444 } + }); + }); + }); + + it('deletes flow value of specified store', function () { + return context.delete({ + scope: 'flow', + id: 'flow1', + store: 'file', + key: 'abc' + }).then(function () { + flowContext.should.eql({ + default: { abc: 333 }, file: {} + }); + }); + }); + + it('deletes node value of default store', function () { + return context.delete({ + scope: 'node', + id: 'known', + store: undefined, // use default + key: 'abc' + }).then(function () { + nodeContext.should.eql({ + default: {}, file: { abc: 666 } + }); + }); + }); + + it('deletes node value of specified store', function () { + return context.delete({ + scope: 'node', + id: 'known', + store: 'file', + key: 'abc' + }).then(function () { + nodeContext.should.eql({ + default: { abc: 555 }, file: {} + }); + }); + }); + + it('does nothing when specified context doesn\'t exist', function() { + return context.delete({ + scope: 'node', + id: 'non-existent', + store: 'file', + key: 'abc' + }).then(function(result) { + should.not.exist(result); + nodeContext.should.eql({ + default: { abc: 555 }, file: { abc: 666 } + }); + }); + }); + + it('404s for unknown store', function (done) { + context.delete({ + scope: 'global', + id: undefined, + store: 'unknown', + key: 'abc' + }).then(function () { + done("delete for unknown store should not resolve"); + }).catch(function (err) { + err.should.have.property('code', 'not_found'); + err.should.have.property('status', 404); + done(); + }); + }); + }); });