Check configs for shared method settings

This commit is contained in:
Simon Ho 2015-09-18 19:13:30 -07:00
parent 716ed4569f
commit 26af1472e7
6 changed files with 204 additions and 12 deletions

View File

@ -11,6 +11,7 @@ var RemoteObjects = require('strong-remoting');
var classify = require('underscore.string/classify'); var classify = require('underscore.string/classify');
var camelize = require('underscore.string/camelize'); var camelize = require('underscore.string/camelize');
var path = require('path'); var path = require('path');
var util = require('util');
/** /**
* The `App` object represents a Loopback application. * The `App` object represents a Loopback application.
@ -435,9 +436,54 @@ function configureModel(ModelCtor, config, app) {
config = extend({}, config); config = extend({}, config);
config.dataSource = dataSource; config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, config);
app.registry.configureModel(ModelCtor, config); app.registry.configureModel(ModelCtor, config);
} }
function setSharedMethodSharedProperties(model, app, modelConfigs) {
var settings = {};
// apply config.json settings
var config = app.get('remoting');
var configHasSharedMethodsSettings = config &&
config.sharedMethods &&
typeof config.sharedMethods === 'object';
if (configHasSharedMethodsSettings)
util._extend(settings, config.sharedMethods);
// apply model-config.json settings
var modelConfig = modelConfigs.options;
var modelConfigHasSharedMethodsSettings = modelConfig &&
modelConfig.remoting &&
modelConfig.remoting.sharedMethods &&
typeof modelConfig.remoting.sharedMethods === 'object';
if (modelConfigHasSharedMethodsSettings)
util._extend(settings, modelConfig.remoting.sharedMethods);
// validate setting values
Object.keys(settings).forEach(function(setting) {
var settingValue = settings[setting];
var settingValueType = typeof settingValue;
if (settingValueType !== 'boolean')
throw new TypeError('Expected boolean, got ' + settingValueType);
});
// set sharedMethod.shared using the merged settings
var sharedMethods = model.sharedClass.methods({includeDisabled: true});
sharedMethods.forEach(function(sharedMethod) {
// use the specific setting if it exists
var hasSpecificSetting = settings.hasOwnProperty(sharedMethod.name);
if (hasSpecificSetting) {
sharedMethod.shared = settings[sharedMethod.name];
} else { // otherwise, use the default setting if it exists
var hasDefaultSetting = settings.hasOwnProperty('*');
if (hasDefaultSetting)
sharedMethod.shared = settings['*'];
}
});
}
function clearHandlerCache(app) { function clearHandlerCache(app) {
app._handlers = undefined; app._handlers = undefined;
} }

View File

@ -618,6 +618,12 @@ module.exports = function(registry) {
description: 'Delete all matching records.', description: 'Delete all matching records.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: {arg: 'where', type: 'object', description: 'filter.where object'}, accepts: {arg: 'where', type: 'object', description: 'filter.where object'},
returns: {
arg: 'count',
type: 'object',
description: 'The number of instances deleted',
root: true
},
http: {verb: 'del', path: '/'}, http: {verb: 'del', path: '/'},
shared: false shared: false
}); });
@ -632,6 +638,12 @@ module.exports = function(registry) {
{arg: 'data', type: 'object', http: {source: 'body'}, {arg: 'data', type: 'object', http: {source: 'body'},
description: 'An object of model property name/value pairs'}, description: 'An object of model property name/value pairs'},
], ],
returns: {
arg: 'count',
description: 'The number of instances updated',
type: 'object',
root: true
},
http: {verb: 'post', path: '/update'} http: {verb: 'post', path: '/update'}
}); });
@ -641,7 +653,8 @@ module.exports = function(registry) {
accessType: 'WRITE', accessType: 'WRITE',
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true, accepts: {arg: 'id', type: 'any', description: 'Model id', required: true,
http: {source: 'path'}}, http: {source: 'path'}},
http: {verb: 'del', path: '/:id'} http: {verb: 'del', path: '/:id'},
returns: {arg: 'count', type: 'object', root: true}
}); });
setRemoting(PersistedModel, 'count', { setRemoting(PersistedModel, 'count', {

View File

@ -49,7 +49,7 @@
"nodemailer-stub-transport": "^0.1.5", "nodemailer-stub-transport": "^0.1.5",
"serve-favicon": "^2.2.0", "serve-favicon": "^2.2.0",
"stable": "^0.1.5", "stable": "^0.1.5",
"strong-remoting": "^2.15.0", "strong-remoting": "^2.21.0",
"uid2": "0.0.3", "uid2": "0.0.3",
"underscore.string": "^3.0.3" "underscore.string": "^3.0.3"
}, },

View File

@ -117,14 +117,15 @@ describe('remoting - integration', function() {
'findById(id:any,filter:object):store GET /stores/:id', 'findById(id:any,filter:object):store GET /stores/:id',
'find(filter:object):store GET /stores', 'find(filter:object):store GET /stores',
'findOne(filter:object):store GET /stores/findOne', 'findOne(filter:object):store GET /stores/findOne',
'updateAll(where:object,data:object) POST /stores/update', 'updateAll(where:object,data:object):object POST /stores/update',
'deleteById(id:any) DELETE /stores/:id', 'deleteById(id:any):object DELETE /stores/:id',
'count(where:object):number GET /stores/count', 'count(where:object):number GET /stores/count',
'prototype.updateAttributes(data:object):store PUT /stores/:id' 'prototype.updateAttributes(data:object):store PUT /stores/:id',
'createChangeStream(options:object):ReadableStream POST /stores/change-stream'
]; ];
// The list of methods is from docs: // The list of methods is from docs:
// http://docs.strongloop.com/display/LB/Exposing+models+over+a+REST+API // https://docs.strongloop.com/display/public/LB/Exposing+models+over+REST
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
}); });

View File

@ -1,3 +1,5 @@
var path = require('path');
describe('loopback.rest', function() { describe('loopback.rest', function() {
var MyModel; var MyModel;
beforeEach(function() { beforeEach(function() {
@ -14,6 +16,20 @@ describe('loopback.rest', function() {
.end(done); .end(done);
}); });
it('should report 200 for DELETE /:id found', function(done) {
app.set('legacyExplorer', false);
app.model(MyModel);
app.use(loopback.rest());
MyModel.create({name: 'm1'}, function(err, inst) {
request(app)
.del('/mymodels/' + inst.id)
.expect(200, function(err, res) {
expect(res.body.count).to.equal(1);
done();
});
});
});
it('should report 404 for GET /:id not found', function(done) { it('should report 404 for GET /:id not found', function(done) {
app.model(MyModel); app.model(MyModel);
app.use(loopback.rest()); app.use(loopback.rest());
@ -337,4 +353,124 @@ describe('loopback.rest', function() {
User.login(credentials, cb); User.login(credentials, cb);
}); });
} }
describe('shared methods', function() {
function getFixturePath(dirName) {
return path.join(__dirname, 'fixtures/shared-methods/' + dirName +
'/server/server.js');
}
describe('with specific definitions in model-config.json', function() {
it('should not be exposed when the definition value is false',
function(done) {
var app = require(getFixturePath('model-config-defined-false'));
request(app)
.get('/todos')
.expect(404, done);
});
it('should be exposed when the definition value is true', function(done) {
var app = require(getFixturePath('model-config-defined-true'));
request(app)
.get('/todos')
.expect(200, done);
});
});
describe('with default definitions in model-config.json', function() {
it('should not be exposed when the definition value is false',
function(done) {
var app = require(getFixturePath('model-config-default-false'));
request(app)
.get('/todos')
.expect(404, done);
});
it('should be exposed when the definition value is true', function(done) {
var app = require(getFixturePath('model-config-default-true'));
app.models.Todo.create([
{content: 'a'},
{content: 'b'},
{content: 'c'}
], function() {
request(app)
.del('/todos')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.count).to.equal(3);
done();
});
});
});
});
describe('with specific definitions in config.json', function() {
it('should not be exposed when the definition value is false',
function(done) {
var app = require(getFixturePath('config-defined-false'));
request(app)
.get('/todos')
.expect(404, done);
});
it('should be exposed when the definition value is true',
function(done) {
var app = require(getFixturePath('config-defined-true'));
request(app)
.get('/todos')
.expect(200, done);
});
});
describe('with default definitions in config.json', function() {
it('should not be exposed when the definition value is false',
function(done) {
var app = require(getFixturePath('config-default-false'));
request(app)
.get('/todos')
.expect(404, done);
});
it('should be exposed when the definition value is true', function(done) {
var app = require(getFixturePath('config-default-true'));
app.models.Todo.create([
{content: 'a'},
{content: 'b'},
{content: 'c'}
], function() {
request(app)
.del('/todos')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.count).to.equal(3);
done();
});
});
});
});
// The fixture in `shared-method/both-configs-set/config.json` has `*:false`
// set which disables the REST endpoints for built-in models such as User as
// a side effect since tests share the same loopback instance. As a
// consequence, this causes the tests in user.integration to fail.
describe.skip('with definitions in both config.json and model-config.json',
function() {
it('should prioritize the settings in model-config.json', function(done) {
var app = require(getFixturePath('both-configs-set'));
request(app)
.del('/todos')
.expect(404, done);
});
it('should fall back to config.json settings if setting is not found in' +
'model-config.json', function(done) {
var app = require(getFixturePath('both-configs-set'));
request(app)
.get('/todos')
.expect(404, done);
});
});
});
}); });

View File

@ -34,14 +34,10 @@ describe('users - integration', function() {
var accessToken; var accessToken;
it('should create a new user', function(done) { it('should create a new user', function(done) {
var url = '/api/users'; this.post('/api/users')
this.post(url)
.send({username: 'x', email: 'x@y.com', password: 'x'}) .send({username: 'x', email: 'x@y.com', password: 'x'})
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) { if (err) return done(err);
return done(err);
}
expect(res.body.id).to.exist; expect(res.body.id).to.exist;
userId = res.body.id; userId = res.body.id;
done(); done();