diff --git a/package.json b/package.json index b80446e..128f417 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "url": "https://github.com/fabito/botkit-storage-datastore/issues" }, "dependencies": { - "gcloud": "^0.32.0" + "google-cloud": "^0.53.0" }, "devDependencies": { "coveralls": "^2.11.9", diff --git a/src/index.js b/src/index.js index 65630f6..ab4cc57 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -var gcloud = require('gcloud'); +var gcloud = require('google-cloud'); /** * The Botkit google cloud datastore driver @@ -12,25 +12,26 @@ module.exports = function(config) { } var datastore = gcloud.datastore(config), + namespace = config.namespace, teamKind = config.teamKind || 'BotkitTeam', channelKind = config.channelKind || 'BotkitChannel', userKind = config.userKind || 'BotkitUser'; return { teams: { - get: get(datastore, teamKind), - save: save(datastore, teamKind), - all: all(datastore, teamKind) + get: get(datastore, teamKind, namespace), + save: save(datastore, teamKind, namespace), + all: all(datastore, teamKind, namespace) }, channels: { - get: get(datastore, channelKind), - save: save(datastore, channelKind), - all: all(datastore, channelKind) + get: get(datastore, channelKind, namespace), + save: save(datastore, channelKind, namespace), + all: all(datastore, channelKind, namespace) }, users: { - get: get(datastore, userKind), - save: save(datastore, userKind), - all: all(datastore, userKind) + get: get(datastore, userKind, namespace), + save: save(datastore, userKind, namespace), + all: all(datastore, userKind, namespace) } }; }; @@ -42,15 +43,22 @@ module.exports = function(config) { * @param {String} kind The entity kind * @returns {Function} The get function */ -function get(datastore, kind) { +function get(datastore, kind, namespace) { return function(id, cb) { - var key = datastore.key([kind, id]); + var keyParam = [kind, id]; + if (namespace) { + keyParam = { + namespace: namespace, + path: keyParam + }; + } + var key = datastore.key(keyParam); datastore.get(key, function(err, entity) { if (err) return cb(err); - return cb(null, entity ? entity.data : null); + return cb(null, entity ? entity : null); }); }; }; @@ -63,9 +71,16 @@ function get(datastore, kind) { * @param {String} kind The entity kind * @returns {Function} The save function */ -function save(datastore, kind) { +function save(datastore, kind, namespace) { return function(data, cb) { - var key = datastore.key([kind, data.id]); + var keyParam = [kind, data.id]; + if (namespace) { + keyParam = { + namespace: namespace, + path: keyParam + }; + } + var key = datastore.key(keyParam); datastore.save({ key: key, // @TODO: convert object to array so that we can exclude properties from indexes @@ -88,16 +103,21 @@ function save(datastore, kind) { * @param {String} kind The entity kind * @returns {Function} The all function */ -function all(datastore, kind) { +function all(datastore, kind, namespace) { return function(cb) { - var query = datastore.createQuery(kind); + var query = null; + if (namespace) { + query = datastore.createQuery(namespace, kind); + } else { + query = datastore.createQuery(kind); + } datastore.runQuery(query, function(err, entities) { if (err) return cb(err); var list = (entities || []).map(function(entity) { - return entity.data; + return entity; }); cb(null, list); diff --git a/tests/index.js b/tests/index.js index e4f9e2f..02bc15b 100644 --- a/tests/index.js +++ b/tests/index.js @@ -26,7 +26,7 @@ describe('Datastore', function() { }; Storage = proxyquire('../src/index', { - gcloud: gcloudMock + 'google-cloud': gcloudMock }); }); @@ -81,7 +81,6 @@ describe('Datastore', function() { config = { projectId: 'right_here' }; - entity = { key: sinon.stub(), data: sinon.stub() @@ -96,7 +95,7 @@ describe('Datastore', function() { datastoreMock.get.callsArgWith(1, null, entity); Storage(config)[method].get('walterwhite', cb); datastoreMock.key.should.be.calledWith([METHODS[method], 'walterwhite']); - cb.should.be.calledWith(null, entity.data); + cb.should.be.calledWith(null, entity); }); it('should handle non existent entity', function() { @@ -117,6 +116,60 @@ describe('Datastore', function() { }); }); + describe('get from namespace', function() { + var records, + entity, + config; + + beforeEach(function() { + config = { + projectId: 'right_here', + namespace: 'my-space' + }; + entity = { + key: sinon.stub(), + data: sinon.stub() + }; + entities = [ + sinon.stub().returns(entity) + ]; + }); + + it('should get entity', function() { + var cb = sinon.stub(); + datastoreMock.get.callsArgWith(1, null, entity); + Storage(config)[method].get('walterwhite', cb); + datastoreMock.key.should.be.calledWith({ + namespace: config.namespace, + path: [METHODS[method], 'walterwhite'] + }); + cb.should.be.calledWith(null, entity); + }); + + it('should handle non existent entity', function() { + var cb = sinon.stub(); + datastoreMock.get.callsArgWith(1, null, null); + Storage(config)[method].get('walterwhite', cb); + datastoreMock.key.should.be.calledWith({ + namespace: config.namespace, + path: [METHODS[method], 'walterwhite'] + }); + cb.should.be.calledWith(null, null); + }); + + it('should call callback on error', function() { + var cb = sinon.stub(), + err = new Error('OOPS'); + datastoreMock.get.callsArgWith(1, err); + Storage(config)[method].get('walterwhite', cb); + datastoreMock.key.should.be.calledWith({ + namespace: config.namespace, + path: [METHODS[method], 'walterwhite'] + }); + cb.should.be.calledWith(err); + }); + }); + describe('save', function() { var config; @@ -137,6 +190,30 @@ describe('Datastore', function() { }); }); + describe('save into namespace', function() { + var config; + + beforeEach(function() { + config = { + projectId: 'right_here', + namespace: 'my-space' + }; + }); + + it('should call datastore save', function() { + var cb = sinon.stub(), + data = {id: 'walterwhite'}, + updateObj = { key: keyMock, data: data}; + + Storage(config)[method].save(data, cb); + datastoreMock.key.should.be.calledWith({ + namespace: config.namespace, + path: [METHODS[method], data.id] + }); + datastoreMock.save.should.be.calledWith(updateObj, cb); + }); + }); + describe('all', function() { var records, @@ -169,7 +246,7 @@ describe('Datastore', function() { it('should get records', function() { var cb = sinon.stub(), - result = [entities[0].data, entities[1].data]; + result = [entities[0], entities[1]]; datastoreMock.runQuery.callsArgWith(1, null, entities); Storage(config)[method].all(cb); @@ -194,5 +271,64 @@ describe('Datastore', function() { cb.should.be.calledWith(err); }); }); + + describe('all from namespace', function() { + + var records, + entities, + config; + + beforeEach(function() { + config = { + projectId: 'right_here', + namespace: 'my-space' + }; + + entities = [ + { + key: 'walterwhite', + data: { + id: 'walterwhite', + name: 'heisenberg' + } + }, + { + key: 'jessepinkman', + data: { + id: 'jessepinkman', + name: 'capncook' + } + } + ]; + + }); + + it('should get records', function() { + var cb = sinon.stub(), + result = [entities[0], entities[1]]; + + datastoreMock.runQuery.callsArgWith(1, null, entities); + Storage(config)[method].all(cb); + datastoreMock.createQuery.should.be.calledWith(config.namespace, METHODS[method]); + cb.should.be.calledWith(null, result); + }); + + it('should handle no records', function() { + var cb = sinon.stub(); + datastoreMock.runQuery.callsArgWith(1, null, undefined); + Storage(config)[method].all(cb); + datastoreMock.createQuery.should.be.calledWith(config.namespace, METHODS[method]); + cb.should.be.calledWith(null, []); + }); + + it('should call callback on error', function() { + var cb = sinon.stub(), + err = new Error('OOPS'); + datastoreMock.runQuery.callsArgWith(1, err); + Storage(config)[method].all(cb); + datastoreMock.createQuery.should.be.calledWith(config.namespace, METHODS[method]); + cb.should.be.calledWith(err); + }); + }); }); });