Merge pull request #2605 from strongloop/feature/key-value-model-2x
common: add KeyValueModel
This commit is contained in:
commit
4c8ad2908b
|
@ -0,0 +1,76 @@
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
|
module.exports = function(KeyValueModel) {
|
||||||
|
// TODO add api docs
|
||||||
|
KeyValueModel.get = function(key, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'get');
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO add api docs
|
||||||
|
KeyValueModel.set = function(key, value, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'set');
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO add api docs
|
||||||
|
KeyValueModel.expire = function(key, ttl, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'expire');
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyValueModel.setup = function() {
|
||||||
|
KeyValueModel.base.setup.apply(this, arguments);
|
||||||
|
|
||||||
|
this.remoteMethod('get', {
|
||||||
|
accepts: {
|
||||||
|
arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' },
|
||||||
|
},
|
||||||
|
returns: { arg: 'value', type: 'any', root: true },
|
||||||
|
http: { path: '/:key', verb: 'get' },
|
||||||
|
rest: { after: convertNullToNotFoundError },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.remoteMethod('set', {
|
||||||
|
accepts: [
|
||||||
|
{ arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' }},
|
||||||
|
{ arg: 'value', type: 'any', required: true,
|
||||||
|
http: { source: 'body' }},
|
||||||
|
{ arg: 'ttl', type: 'number',
|
||||||
|
http: { source: 'query' },
|
||||||
|
description: 'time to live in milliseconds' },
|
||||||
|
],
|
||||||
|
http: { path: '/:key', verb: 'put' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.remoteMethod('expire', {
|
||||||
|
accepts: [
|
||||||
|
{ arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' }},
|
||||||
|
{ arg: 'ttl', type: 'number', required: true,
|
||||||
|
http: { source: 'form' }},
|
||||||
|
],
|
||||||
|
http: { path: '/:key/expire', verb: 'put' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function throwNotAttached(modelName, methodName) {
|
||||||
|
throw new Error(g.f(
|
||||||
|
'Cannot call %s.%s(). ' +
|
||||||
|
'The %s method has not been setup. ' +
|
||||||
|
'The {{KeyValueModel}} has not been correctly attached ' +
|
||||||
|
'to a {{DataSource}}!',
|
||||||
|
modelName, methodName, methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertNullToNotFoundError(ctx, cb) {
|
||||||
|
if (ctx.result !== null) return cb();
|
||||||
|
|
||||||
|
var modelName = ctx.method.sharedClass.name;
|
||||||
|
var id = ctx.getArgByName('id');
|
||||||
|
var msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id);
|
||||||
|
var error = new Error(msg);
|
||||||
|
error.statusCode = error.status = 404;
|
||||||
|
error.code = 'KEY_NOT_FOUND';
|
||||||
|
cb(error);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "KeyValueModel",
|
||||||
|
"base": "Model"
|
||||||
|
}
|
|
@ -6,6 +6,10 @@
|
||||||
module.exports = function(registry) {
|
module.exports = function(registry) {
|
||||||
// NOTE(bajtos) we must use static require() due to browserify limitations
|
// NOTE(bajtos) we must use static require() due to browserify limitations
|
||||||
|
|
||||||
|
registry.KeyValueModel = createModel(
|
||||||
|
require('../common/models/key-value-model.json'),
|
||||||
|
require('../common/models/key-value-model.js'));
|
||||||
|
|
||||||
registry.Email = createModel(
|
registry.Email = createModel(
|
||||||
require('../common/models/email.json'),
|
require('../common/models/email.json'),
|
||||||
require('../common/models/email.js'));
|
require('../common/models/email.js'));
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var http = require('http');
|
||||||
|
var loopback = require('..');
|
||||||
|
var supertest = require('supertest');
|
||||||
|
|
||||||
|
var AN_OBJECT_VALUE = { name: 'an-object' };
|
||||||
|
|
||||||
|
describe('KeyValueModel', function() {
|
||||||
|
var request, app, CacheItem;
|
||||||
|
beforeEach(setupAppAndCacheItem);
|
||||||
|
|
||||||
|
describe('REST API', function() {
|
||||||
|
before(setupSharedHttpServer);
|
||||||
|
|
||||||
|
it('provides "get(key)" at "GET /key"', function(done) {
|
||||||
|
CacheItem.set('get-key', AN_OBJECT_VALUE);
|
||||||
|
request.get('/CacheItems/get-key')
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.body).to.eql(AN_OBJECT_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 404 when getting a key that does not exist', function(done) {
|
||||||
|
request.get('/CacheItems/key-does-not-exist')
|
||||||
|
.expect(404, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides "set(key)" at "PUT /key"', function(done) {
|
||||||
|
request.put('/CacheItems/set-key')
|
||||||
|
.send(AN_OBJECT_VALUE)
|
||||||
|
.expect(204)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
CacheItem.get('set-key', function(err, value) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(value).to.eql(AN_OBJECT_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides "set(key, ttl)" at "PUT /key?ttl={num}"', function(done) {
|
||||||
|
request.put('/CacheItems/set-key-ttl?ttl=10')
|
||||||
|
.send(AN_OBJECT_VALUE)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
setTimeout(function() {
|
||||||
|
CacheItem.get('set-key-ttl', function(err, value) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(value).to.equal(null);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides "expire(key, ttl)" at "PUT /key/expire"',
|
||||||
|
function(done) {
|
||||||
|
CacheItem.set('expire-key', AN_OBJECT_VALUE, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
request.put('/CacheItems/expire-key/expire')
|
||||||
|
.send({ ttl: 10 })
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
setTimeout(function() {
|
||||||
|
CacheItem.get('set-key-ttl', function(err, value) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(value).to.equal(null);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 404 when expiring a key that does not exist', function(done) {
|
||||||
|
request.put('/CacheItems/key-does-not-exist/expire')
|
||||||
|
.send({ ttl: 10 })
|
||||||
|
.expect(404, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupAppAndCacheItem() {
|
||||||
|
app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
||||||
|
app.use(loopback.rest());
|
||||||
|
|
||||||
|
CacheItem = app.registry.createModel({
|
||||||
|
name: 'CacheItem',
|
||||||
|
base: 'KeyValueModel',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.dataSource('kv', { connector: 'kv-memory' });
|
||||||
|
app.model(CacheItem, { dataSource: 'kv' });
|
||||||
|
}
|
||||||
|
|
||||||
|
var _server, _requestHandler; // eslint-disable-line one-var
|
||||||
|
function setupSharedHttpServer(done) {
|
||||||
|
_server = http.createServer(function(req, res) {
|
||||||
|
app(req, res);
|
||||||
|
});
|
||||||
|
_server.listen(0, '127.0.0.1')
|
||||||
|
.once('listening', function() {
|
||||||
|
request = supertest('http://127.0.0.1:' + this.address().port);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.once('error', function(err) { done(err); });
|
||||||
|
}
|
||||||
|
});
|
|
@ -45,6 +45,7 @@ describe('loopback', function() {
|
||||||
'DataSource',
|
'DataSource',
|
||||||
'Email',
|
'Email',
|
||||||
'GeoPoint',
|
'GeoPoint',
|
||||||
|
'KeyValueModel',
|
||||||
'Mail',
|
'Mail',
|
||||||
'Memory',
|
'Memory',
|
||||||
'Model',
|
'Model',
|
||||||
|
|
Loading…
Reference in New Issue