Merge branch 'master' into issue414_token
This commit is contained in:
commit
2d7511a6cf
140
CHANGES.md
140
CHANGES.md
|
@ -1,3 +1,135 @@
|
|||
2016-06-13, Version 3.0.0-alpha.2
|
||||
=================================
|
||||
|
||||
* add missing unit tests for #2108 (Benjamin Kroeger)
|
||||
|
||||
* Expose `Replace*` methods (Amir Jafarian)
|
||||
|
||||
* Update tests for strong-error-handler (David Cheung)
|
||||
|
||||
* Remove legacy express 3.x middleware getters (Miroslav Bajtoš)
|
||||
|
||||
* Docuemtation for `replace*` methods (Amir Jafarian)
|
||||
|
||||
* Make the doc clear for `findORCreate` cb (Amir Jafarian)
|
||||
|
||||
* Fix JSCS unsupported rule error (Jason)
|
||||
|
||||
* Remove env.json and strong-pm dir (Ritchie Martori)
|
||||
|
||||
* Throw error upon extending unknown model (David Cheung)
|
||||
|
||||
* Remove unused UserModel properties (David Cheung)
|
||||
|
||||
* Remove Change.handleError (Candy)
|
||||
|
||||
* Update user.js (Rik)
|
||||
|
||||
* Separate error-checking and next/done logic from other logic in the test suite (Supasate Choochaisri)
|
||||
|
||||
* Clean up by removing unnecessary comments (Supasate Choochaisri)
|
||||
|
||||
* Add feature to not allow duplicate role name (Supasate Choochaisri)
|
||||
|
||||
* update copyright statements (Ryan Graham)
|
||||
|
||||
* relicense as MIT only (Ryan Graham)
|
||||
|
||||
* Upgrade phantomjs to 2.x (Miroslav Bajtoš)
|
||||
|
||||
* app: send port:0 instead of port:undefined (Miroslav Bajtoš)
|
||||
|
||||
* travis: drop node@5, add node@6 (Miroslav Bajtoš)
|
||||
|
||||
* Disable DEBUG output for eslint on Jenkins CI (Miroslav Bajtoš)
|
||||
|
||||
* Remove "loopback.autoAttach()" (Miroslav Bajtoš)
|
||||
|
||||
* test/rest.middleware: use local registry (Miroslav Bajtoš)
|
||||
|
||||
* Fix role.isOwner to support app-local registry (Miroslav Bajtoš)
|
||||
|
||||
* test/user: use local registry (Miroslav Bajtoš)
|
||||
|
||||
* Resolver support return promise (juehou)
|
||||
|
||||
* remove @private from jsdoc (Manu Phatak)
|
||||
|
||||
* Fixes for emit `remoteMethodDisabled` PR (Simon Ho)
|
||||
|
||||
* Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri)
|
||||
|
||||
* Fix typo in Model.nestRemoting (Tim Needham)
|
||||
|
||||
* Update loopback.js (Rand McKinney)
|
||||
|
||||
* Allow built-in token middleware to run repeatedly (Benjamin Kröger)
|
||||
|
||||
* Use eslint with loopback config (Miroslav Bajtoš)
|
||||
|
||||
* promise docs (Jue Hou)
|
||||
|
||||
* Update JSDoc (sghung@ca.ibm.com)
|
||||
|
||||
* Remove constraint making isStatic required (Candy)
|
||||
|
||||
* Fix inconsistencies in JSDoc (sghung@ca.ibm.com)
|
||||
|
||||
* Improve error message on connector init error (Miroslav Bajtoš)
|
||||
|
||||
* application: correct spelling of "cannont" (Sam Roberts)
|
||||
|
||||
* Remove sl-blip from dependency (Candy)
|
||||
|
||||
* Use new strong-remoting API (Candy)
|
||||
|
||||
* test: remove forgotten console.trace logs (Miroslav Bajtoš)
|
||||
|
||||
* Fix race condition in replication tests (Miroslav Bajtoš)
|
||||
|
||||
* Fix race condition in error handler test (Miroslav Bajtoš)
|
||||
|
||||
* test: remove errant console.log from test (Ryan Graham)
|
||||
|
||||
* Promisify Model Change (Jue Hou)
|
||||
|
||||
* Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš)
|
||||
|
||||
* test: use ephemeral port for e2e server (Ryan Graham)
|
||||
|
||||
* test: fail on error instead of crash (Ryan Graham)
|
||||
|
||||
* ensure app is booted before integration tests (Ryan Graham)
|
||||
|
||||
* Remove "loopback.DataModel" (Miroslav Bajtoš)
|
||||
|
||||
* Correct JSDoc findOrCreate() callback in PersistedModel (Chris Coggburn)
|
||||
|
||||
* Fix typo in package.json (publishConfig) (Miroslav Bajtoš)
|
||||
|
||||
* Start development of 3.0 (Candy)
|
||||
|
||||
* Hide verificationToken (Samuel Gaus)
|
||||
|
||||
* Fix description for User.prototype.hasPassword (Jue Hou)
|
||||
|
||||
* Checkpoint speedup (Amir Jafarian)
|
||||
|
||||
* Always use bluebird as promise library Replace `global.Promise` with `bluebird` (Jue Hou)
|
||||
|
||||
* Remove unused code from loopback-testing-helper (Simon Ho)
|
||||
|
||||
* Make juggler a regular dependency (Miroslav Bajtoš)
|
||||
|
||||
* Remove dependency on loopback-testing (Simon Ho)
|
||||
|
||||
* Fix failing tests (Simon Ho)
|
||||
|
||||
* Update persisted-model.js (Rand McKinney)
|
||||
|
||||
* Update persisted-model.js (linguofeng)
|
||||
|
||||
|
||||
2015-12-22, Version 3.0.0-alpha.1
|
||||
=================================
|
||||
|
||||
|
@ -997,8 +1129,6 @@
|
|||
2014-07-15, Version 2.0.0-beta6
|
||||
===============================
|
||||
|
||||
* 2.0.0-beta6 (Miroslav Bajtoš)
|
||||
|
||||
* lib/application: publish Change models to REST API (Miroslav Bajtoš)
|
||||
|
||||
* models/change: fix typo (Miroslav Bajtoš)
|
||||
|
@ -1009,8 +1139,6 @@
|
|||
2014-07-03, Version 2.0.0-beta5
|
||||
===============================
|
||||
|
||||
* 2.0.0-beta5 (Miroslav Bajtoš)
|
||||
|
||||
* app: update `url` on `listening` event (Miroslav Bajtoš)
|
||||
|
||||
* Fix "ReferenceError: loopback is not defined" in registry.memory(). (Guilherme Cirne)
|
||||
|
@ -1029,8 +1157,6 @@
|
|||
2014-06-26, Version 2.0.0-beta4
|
||||
===============================
|
||||
|
||||
* 2.0.0-beta4 (Miroslav Bajtoš)
|
||||
|
||||
* package: upgrade juggler to 2.0.0-beta2 (Miroslav Bajtoš)
|
||||
|
||||
* Fix loopback in PhantomJS, fix karma tests (Miroslav Bajtoš)
|
||||
|
@ -1157,8 +1283,6 @@
|
|||
2014-05-28, Version 2.0.0-beta3
|
||||
===============================
|
||||
|
||||
* 2.0.0-beta3 (Miroslav Bajtoš)
|
||||
|
||||
* package.json: fix malformed json (Miroslav Bajtoš)
|
||||
|
||||
* 2.0.0-beta2 (Ritchie Martori)
|
||||
|
|
|
@ -64,6 +64,12 @@
|
|||
"permission": "ALLOW",
|
||||
"property": "updateAttributes"
|
||||
},
|
||||
{
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$owner",
|
||||
"permission": "ALLOW",
|
||||
"property": "replaceById"
|
||||
},
|
||||
{
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
|
|
|
@ -111,7 +111,8 @@ module.exports = function(registry) {
|
|||
* @param {Object} model Updated model instance.
|
||||
*/
|
||||
|
||||
PersistedModel.upsert = PersistedModel.updateOrCreate = function upsert(data, callback) {
|
||||
PersistedModel.upsert = PersistedModel.updateOrCreate = PersistedModel.patchOrCreate =
|
||||
function upsert(data, callback) {
|
||||
throwNotAttached(this.modelName, 'upsert');
|
||||
};
|
||||
|
||||
|
@ -494,7 +495,8 @@ module.exports = function(registry) {
|
|||
* @param {Object} instance Updated instance.
|
||||
*/
|
||||
|
||||
PersistedModel.prototype.updateAttributes = function updateAttributes(data, cb) {
|
||||
PersistedModel.prototype.updateAttributes = PersistedModel.prototype.patchAttributes =
|
||||
function updateAttributes(data, cb) {
|
||||
throwNotAttached(this.modelName, 'updateAttributes');
|
||||
};
|
||||
|
||||
|
@ -600,6 +602,9 @@ module.exports = function(registry) {
|
|||
var typeName = PersistedModel.modelName;
|
||||
var options = PersistedModel.settings;
|
||||
|
||||
// This is just for LB 3.x
|
||||
options.replaceOnPUT = options.replaceOnPUT !== false;
|
||||
|
||||
function setRemoting(scope, name, options) {
|
||||
var fn = scope[name];
|
||||
fn._delegate = true;
|
||||
|
@ -616,15 +621,35 @@ module.exports = function(registry) {
|
|||
http: { verb: 'post', path: '/' },
|
||||
});
|
||||
|
||||
setRemoting(PersistedModel, 'upsert', {
|
||||
aliases: ['updateOrCreate'],
|
||||
description: 'Update an existing model instance or insert a new one into the data source.',
|
||||
var upsertOptions = {
|
||||
aliases: ['upsert', 'updateOrCreate'],
|
||||
description: 'Patch an existing model instance or insert a new one into the data source.',
|
||||
accessType: 'WRITE',
|
||||
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
|
||||
'Model instance data' },
|
||||
returns: { arg: 'data', type: typeName, root: true },
|
||||
http: { verb: 'put', path: '/' },
|
||||
});
|
||||
http: [{ verb: 'patch', path: '/' }],
|
||||
};
|
||||
|
||||
if (!options.replaceOnPUT) {
|
||||
upsertOptions.http.push({ verb: 'put', path: '/' });
|
||||
}
|
||||
setRemoting(PersistedModel, 'patchOrCreate', upsertOptions);
|
||||
|
||||
var replaceOrCreateOptions = {
|
||||
description: 'Replace an existing model instance or insert a new one into the data source.',
|
||||
accessType: 'WRITE',
|
||||
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
|
||||
'Model instance data' },
|
||||
returns: { arg: 'data', type: typeName, root: true },
|
||||
http: [{ verb: 'post', path: '/replaceOrCreate' }],
|
||||
};
|
||||
|
||||
if (options.replaceOnPUT) {
|
||||
replaceOrCreateOptions.http.push({ verb: 'put', path: '/' });
|
||||
}
|
||||
|
||||
setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions);
|
||||
|
||||
setRemoting(PersistedModel, 'exists', {
|
||||
description: 'Check whether a model instance exists in the data source.',
|
||||
|
@ -671,6 +696,26 @@ module.exports = function(registry) {
|
|||
rest: { after: convertNullToNotFoundError },
|
||||
});
|
||||
|
||||
var replaceByIdOptions = {
|
||||
description: 'Replace attributes for a model instance and persist it into the data source.',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{ arg: 'id', type: 'any', description: 'Model id', required: true,
|
||||
http: { source: 'path' }},
|
||||
{ arg: 'data', type: 'object', http: { source: 'body' }, description:
|
||||
'Model instance data' },
|
||||
],
|
||||
returns: { arg: 'data', type: typeName, root: true },
|
||||
http: [{ verb: 'post', path: '/:id/replace' }],
|
||||
};
|
||||
|
||||
if (options.replaceOnPUT) {
|
||||
replaceByIdOptions.http.push({ verb: 'put', path: '/:id' });
|
||||
}
|
||||
|
||||
setRemoting(PersistedModel, 'replaceById', replaceByIdOptions);
|
||||
|
||||
|
||||
setRemoting(PersistedModel, 'find', {
|
||||
description: 'Find all instances of the model matched by filter from the data source.',
|
||||
accessType: 'READ',
|
||||
|
@ -741,13 +786,20 @@ module.exports = function(registry) {
|
|||
http: { verb: 'get', path: '/count' },
|
||||
});
|
||||
|
||||
setRemoting(PersistedModel.prototype, 'updateAttributes', {
|
||||
description: 'Update attributes for a model instance and persist it into the data source.',
|
||||
var updateAttributesOptions = {
|
||||
aliases: ['updateAttributes'],
|
||||
description: 'Patch attributes for a model instance and persist it into the data source.',
|
||||
accessType: 'WRITE',
|
||||
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'An object of model property name/value pairs' },
|
||||
returns: { arg: 'data', type: typeName, root: true },
|
||||
http: { verb: 'put', path: '/' },
|
||||
});
|
||||
http: [{ verb: 'patch', path: '/' }],
|
||||
};
|
||||
|
||||
setRemoting(PersistedModel.prototype, 'patchAttributes', updateAttributesOptions);
|
||||
|
||||
if (!options.replaceOnPUT) {
|
||||
updateAttributesOptions.http.push({ verb: 'put', path: '/' });
|
||||
}
|
||||
|
||||
if (options.trackChanges || options.enableRemoteReplication) {
|
||||
setRemoting(PersistedModel, 'diff', {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback",
|
||||
"version": "3.0.0-alpha.1",
|
||||
"version": "3.0.0-alpha.2",
|
||||
"publishConfig": {
|
||||
"tag": "next"
|
||||
},
|
||||
|
|
|
@ -111,9 +111,15 @@ describe('access control - integration', function() {
|
|||
assert.equal(user.password, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
// user has replaceOnPUT = false; so then both PUT and PATCH should be allowed for update
|
||||
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('PATCH', '/api/users/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
});
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
|
||||
|
@ -163,7 +169,7 @@ describe('access control - integration', function() {
|
|||
}
|
||||
});
|
||||
|
||||
describe('/accounts', function() {
|
||||
describe('/accounts with replaceOnPUT true', function() {
|
||||
var count = 0;
|
||||
before(function() {
|
||||
var roleModel = loopback.getModelByType(loopback.Role);
|
||||
|
@ -177,48 +183,68 @@ describe('access control - integration', function() {
|
|||
});
|
||||
});
|
||||
|
||||
lt.beforeEach.givenModel('account');
|
||||
lt.beforeEach.givenModel('accountWithReplaceOnPUTtrue');
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts-replacing');
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts-replacing');
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts-replacing');
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts');
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts-replacing');
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts-replacing');
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts-replacing');
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
|
||||
|
||||
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||
var actId;
|
||||
beforeEach(function(done) {
|
||||
var self = this;
|
||||
|
||||
// Create an account under the given user
|
||||
app.models.account.create({
|
||||
app.models.accountWithReplaceOnPUTtrue.create({
|
||||
userId: self.user.id,
|
||||
balance: 100,
|
||||
}, function(err, act) {
|
||||
self.url = '/api/accounts/' + act.id;
|
||||
|
||||
actId = act.id;
|
||||
self.url = '/api/accounts-replacing/' + actId;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
|
||||
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-replacing/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('GET', '/api/accounts/:id', function() {
|
||||
lt.describe.whenCalledRemotely('PUT', '/api/accounts-replacing/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('DELETE', '/api/accounts/:id', function() {
|
||||
lt.describe.whenCalledRemotely('GET', '/api/accounts-replacing/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-replacing/:id', function() {
|
||||
lt.it.shouldBeDenied();
|
||||
});
|
||||
describe('replace on POST verb', function() {
|
||||
beforeEach(function(done) {
|
||||
this.url = '/api/accounts-replacing/' + actId + '/replace';
|
||||
done();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('POST', '/api/accounts-replacing/:id/replace', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
||||
|
@ -226,7 +252,76 @@ describe('access control - integration', function() {
|
|||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
||||
|
||||
function urlForAccount() {
|
||||
return '/api/accounts/' + this.account.id;
|
||||
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id;
|
||||
}
|
||||
function urlForReplaceAccountPOST() {
|
||||
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id + '/replace';
|
||||
}
|
||||
});
|
||||
|
||||
describe('/accounts with replaceOnPUT false', function() {
|
||||
lt.beforeEach.givenModel('accountWithReplaceOnPUTfalse');
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
|
||||
|
||||
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||
var actId;
|
||||
beforeEach(function(done) {
|
||||
var self = this;
|
||||
// Create an account under the given user
|
||||
app.models.accountWithReplaceOnPUTfalse.create({
|
||||
userId: self.user.id,
|
||||
balance: 100,
|
||||
}, function(err, act) {
|
||||
actId = act.id;
|
||||
self.url = '/api/accounts-updating/' + actId;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-updating/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('PUT', '/api/accounts-updating/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('GET', '/api/accounts-updating/:id', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-updating/:id', function() {
|
||||
lt.it.shouldBeDenied();
|
||||
});
|
||||
|
||||
describe('replace on POST verb', function() {
|
||||
beforeEach(function(done) {
|
||||
this.url = '/api/accounts-updating/' + actId + '/replace';
|
||||
done();
|
||||
});
|
||||
lt.describe.whenCalledRemotely('POST', '/api/accounts-updating/:id/replace', function() {
|
||||
lt.it.shouldBeAllowed();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);
|
||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
||||
|
||||
function urlForAccount() {
|
||||
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id;
|
||||
}
|
||||
function urlForReplaceAccountPOST() {
|
||||
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id + '/replace';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -267,6 +267,40 @@ describe('loopback.token(options)', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should overwrite invalid existing token (is !== undefined and has no "id" property) ' +
|
||||
' when enableDoubkecheck is true',
|
||||
function(done) {
|
||||
var token = this.token;
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
req.accessToken = null;
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(loopback.token({
|
||||
model: Token,
|
||||
enableDoublecheck: true,
|
||||
}));
|
||||
|
||||
app.get('/', function(req, res, next) {
|
||||
res.send(req.accessToken);
|
||||
});
|
||||
|
||||
request(app).get('/')
|
||||
.set('Authorization', token.id)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body).to.eql({
|
||||
id: token.id,
|
||||
ttl: token.ttl,
|
||||
userId: token.userId,
|
||||
created: token.created.toJSON(),
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite existing token when enableDoublecheck ' +
|
||||
'and overwriteExistingToken options are truthy',
|
||||
function(done) {
|
||||
|
|
|
@ -10,16 +10,8 @@ var request = require('supertest');
|
|||
var expect = require('chai').expect;
|
||||
|
||||
describe('loopback.errorHandler(options)', function() {
|
||||
it('should throw a descriptive error', function(done) {
|
||||
try {
|
||||
//arrange
|
||||
var app = loopback();
|
||||
app.use(loopback.urlNotFound());
|
||||
app.use(loopback.errorHandler({ log: false }));
|
||||
} catch (e) {
|
||||
expect(function() { loopback.errorHandler(); })
|
||||
it('should throw a descriptive error', function() {
|
||||
expect(function() { loopback.errorHandler(); })
|
||||
.to.throw(/no longer available.*strong-error-handler/);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "account",
|
||||
"name": "accountWithReplaceOnPUTtrue",
|
||||
"plural": "accounts-replacing",
|
||||
"relations": {
|
||||
"transactions": {
|
||||
"model": "transaction",
|
||||
|
@ -38,5 +39,6 @@
|
|||
"principalId": "$dummy"
|
||||
}
|
||||
],
|
||||
"properties": {}
|
||||
"properties": {},
|
||||
"replaceOnPUT": true
|
||||
}
|
44
test/fixtures/access-control/common/models/accountWithReplaceOnPUTfalse.json
vendored
Normal file
44
test/fixtures/access-control/common/models/accountWithReplaceOnPUTfalse.json
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "accountWithReplaceOnPUTfalse",
|
||||
"plural": "accounts-updating",
|
||||
"relations": {
|
||||
"transactions": {
|
||||
"model": "transaction",
|
||||
"type": "hasMany"
|
||||
},
|
||||
"user": {
|
||||
"model": "user",
|
||||
"type": "belongsTo",
|
||||
"foreignKey": "userId"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "*",
|
||||
"permission": "DENY",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone"
|
||||
},
|
||||
{
|
||||
"accessType": "*",
|
||||
"permission": "ALLOW",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$owner"
|
||||
},
|
||||
{
|
||||
"permission": "DENY",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$owner",
|
||||
"property": "deleteById"
|
||||
},
|
||||
{
|
||||
"accessType": "*",
|
||||
"permission": "DENY",
|
||||
"property": "find",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$dummy"
|
||||
}
|
||||
],
|
||||
"properties": {},
|
||||
"replaceOnPUT": false
|
||||
}
|
|
@ -19,5 +19,6 @@
|
|||
"principalType": "ROLE",
|
||||
"principalId": "$everyone"
|
||||
}
|
||||
]
|
||||
],
|
||||
"replaceOnPUT": false
|
||||
}
|
|
@ -33,7 +33,11 @@
|
|||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
"account": {
|
||||
"accountWithReplaceOnPUTtrue": {
|
||||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
"accountWithReplaceOnPUTfalse": {
|
||||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "storeWithReplaceOnPUTtrue",
|
||||
"plural": "stores-replacing",
|
||||
"properties": {},
|
||||
"scopes": {
|
||||
"superStores": {
|
||||
"where": {
|
||||
"size": "super"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"widgets": {
|
||||
"model": "widget",
|
||||
"type": "hasMany"
|
||||
}
|
||||
},
|
||||
"replaceOnPUT": true
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "storeWithReplaceOnPUTfalse",
|
||||
"plural": "stores-updating",
|
||||
"properties": {},
|
||||
"scopes": {
|
||||
"superStores": {
|
||||
"where": {
|
||||
"size": "super"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"widgets": {
|
||||
"model": "widget",
|
||||
"type": "hasMany"
|
||||
}
|
||||
},
|
||||
"replaceOnPUT": false
|
||||
}
|
|
@ -37,6 +37,14 @@
|
|||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
"storeWithReplaceOnPUTfalse": {
|
||||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
"storeWithReplaceOnPUTtrue": {
|
||||
"public": true,
|
||||
"dataSource": "db"
|
||||
},
|
||||
"physician": {
|
||||
"dataSource": "db",
|
||||
"public": true
|
||||
|
|
|
@ -624,9 +624,14 @@ describe.onServer('Remote Methods', function() {
|
|||
var methodNames = [];
|
||||
metadata.methods.forEach(function(method) {
|
||||
methodNames.push(method.name);
|
||||
methodNames = methodNames.concat(method.sharedMethod.aliases || []);
|
||||
var aliases = method.sharedMethod.aliases;
|
||||
if (method.name.indexOf('prototype.') === 0) {
|
||||
aliases = aliases.map(function(alias) {
|
||||
return 'prototype.' + alias;
|
||||
});
|
||||
}
|
||||
methodNames = methodNames.concat(aliases || []);
|
||||
});
|
||||
|
||||
expect(methodNames).to.have.members([
|
||||
// NOTE(bajtos) These three methods are disabled by default
|
||||
// Because all tests share the same global registry model
|
||||
|
@ -634,9 +639,11 @@ describe.onServer('Remote Methods', function() {
|
|||
// this test was seeing this method (with all aliases) as public
|
||||
// 'destroyAll', 'deleteAll', 'remove',
|
||||
'create',
|
||||
'upsert', 'updateOrCreate',
|
||||
'upsert', 'updateOrCreate', 'patchOrCreate',
|
||||
'exists',
|
||||
'findById',
|
||||
'replaceById',
|
||||
'replaceOrCreate',
|
||||
'find',
|
||||
'findOne',
|
||||
'updateAll', 'update',
|
||||
|
@ -644,7 +651,7 @@ describe.onServer('Remote Methods', function() {
|
|||
'destroyById',
|
||||
'removeById',
|
||||
'count',
|
||||
'prototype.updateAttributes',
|
||||
'prototype.patchAttributes', 'prototype.updateAttributes',
|
||||
'createChangeStream',
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -74,60 +74,27 @@ describe('remoting - integration', function() {
|
|||
});
|
||||
|
||||
describe('Model shared classes', function() {
|
||||
function formatReturns(m) {
|
||||
var returns = m.returns;
|
||||
if (!returns || returns.length === 0) {
|
||||
return '';
|
||||
}
|
||||
var type = returns[0].type;
|
||||
return type ? ':' + type : '';
|
||||
}
|
||||
|
||||
function formatMethod(m) {
|
||||
return [
|
||||
m.name,
|
||||
'(',
|
||||
m.accepts.map(function(a) {
|
||||
return a.arg + ':' + a.type;
|
||||
}).join(','),
|
||||
')',
|
||||
formatReturns(m),
|
||||
' ',
|
||||
m.getHttpMethod(),
|
||||
' ',
|
||||
m.getFullPath(),
|
||||
].join('');
|
||||
}
|
||||
|
||||
function findClass(name) {
|
||||
return app.handler('rest').adapter
|
||||
.getClasses()
|
||||
.filter(function(c) {
|
||||
return c.name === name;
|
||||
})[0];
|
||||
}
|
||||
|
||||
it('has expected remote methods', function() {
|
||||
it('has expected remote methods with default model.settings.replaceOnPUT' +
|
||||
'set to true (3.x)',
|
||||
function() {
|
||||
var storeClass = findClass('store');
|
||||
var methods = storeClass.methods
|
||||
.filter(function(m) {
|
||||
return m.name.indexOf('__') === -1;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
});
|
||||
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'create(data:object):store POST /stores',
|
||||
'upsert(data:object):store PUT /stores',
|
||||
'patchOrCreate(data:object):store PATCH /stores',
|
||||
'replaceOrCreate(data:object):store PUT /stores',
|
||||
'replaceOrCreate(data:object):store POST /stores/replaceOrCreate',
|
||||
'exists(id:any):boolean GET /stores/:id/exists',
|
||||
'findById(id:any,filter:object):store GET /stores/:id',
|
||||
'replaceById(id:any,data:object):store PUT /stores/:id',
|
||||
'replaceById(id:any,data:object):store POST /stores/:id/replace',
|
||||
'find(filter:object):store GET /stores',
|
||||
'findOne(filter:object):store GET /stores/findOne',
|
||||
'updateAll(where:object,data:object):object POST /stores/update',
|
||||
'deleteById(id:any):object DELETE /stores/:id',
|
||||
'count(where:object):number GET /stores/count',
|
||||
'prototype.updateAttributes(data:object):store PUT /stores/:id',
|
||||
'prototype.patchAttributes(data:object):store PATCH /stores/:id',
|
||||
'createChangeStream(options:object):ReadableStream POST /stores/change-stream',
|
||||
];
|
||||
|
||||
|
@ -138,13 +105,7 @@ describe('remoting - integration', function() {
|
|||
|
||||
it('has expected remote methods for scopes', function() {
|
||||
var storeClass = findClass('store');
|
||||
var methods = storeClass.methods
|
||||
.filter(function(m) {
|
||||
return m.name.indexOf('__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
});
|
||||
var methods = getFormattedScopeMethods(storeClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'__get__superStores(filter:object):store GET /stores/superStores',
|
||||
|
@ -159,13 +120,7 @@ describe('remoting - integration', function() {
|
|||
it('should have correct signatures for belongsTo methods',
|
||||
function() {
|
||||
var widgetClass = findClass('widget');
|
||||
var methods = widgetClass.methods
|
||||
.filter(function(m) {
|
||||
return m.name.indexOf('prototype.__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
});
|
||||
var methods = getFormattedPrototypeMethods(widgetClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'prototype.__get__store(refresh:boolean):store ' +
|
||||
|
@ -177,13 +132,7 @@ describe('remoting - integration', function() {
|
|||
it('should have correct signatures for hasMany methods',
|
||||
function() {
|
||||
var physicianClass = findClass('store');
|
||||
var methods = physicianClass.methods
|
||||
.filter(function(m) {
|
||||
return m.name.indexOf('prototype.__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
});
|
||||
var methods = getFormattedPrototypeMethods(physicianClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'prototype.__findById__widgets(fk:any):widget ' +
|
||||
|
@ -207,13 +156,7 @@ describe('remoting - integration', function() {
|
|||
it('should have correct signatures for hasMany-through methods',
|
||||
function() { // jscs:disable validateIndentation
|
||||
var physicianClass = findClass('physician');
|
||||
var methods = physicianClass.methods
|
||||
.filter(function(m) {
|
||||
return m.name.indexOf('prototype.__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
});
|
||||
var methods = getFormattedPrototypeMethods(physicianClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'prototype.__findById__patients(fk:any):patient ' +
|
||||
|
@ -241,3 +184,127 @@ describe('remoting - integration', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('With model.settings.replaceOnPUT false', function() {
|
||||
lt.beforeEach.withApp(app);
|
||||
lt.beforeEach.givenModel('storeWithReplaceOnPUTfalse');
|
||||
afterEach(function(done) {
|
||||
this.app.models.storeWithReplaceOnPUTfalse.destroyAll(done);
|
||||
});
|
||||
|
||||
it('should have expected remote methods',
|
||||
function() {
|
||||
var storeClass = findClass('storeWithReplaceOnPUTfalse');
|
||||
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating',
|
||||
'patchOrCreate(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating',
|
||||
'replaceOrCreate(data:object):storeWithReplaceOnPUTfalse POST /stores-updating/replaceOrCreate',
|
||||
'replaceById(id:any,data:object):storeWithReplaceOnPUTfalse POST /stores-updating/:id/replace',
|
||||
'prototype.patchAttributes(data:object):storeWithReplaceOnPUTfalse PATCH /stores-updating/:id',
|
||||
'prototype.patchAttributes(data:object):storeWithReplaceOnPUTfalse PUT /stores-updating/:id',
|
||||
];
|
||||
|
||||
expect(methods).to.include.members(expectedMethods);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With model.settings.replaceOnPUT true', function() {
|
||||
lt.beforeEach.withApp(app);
|
||||
lt.beforeEach.givenModel('storeWithReplaceOnPUTtrue');
|
||||
afterEach(function(done) {
|
||||
this.app.models.storeWithReplaceOnPUTtrue.destroyAll(done);
|
||||
});
|
||||
|
||||
it('should have expected remote methods',
|
||||
function() {
|
||||
var storeClass = findClass('storeWithReplaceOnPUTtrue');
|
||||
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);
|
||||
|
||||
var expectedMethods = [
|
||||
'patchOrCreate(data:object):storeWithReplaceOnPUTtrue PATCH /stores-replacing',
|
||||
'replaceOrCreate(data:object):storeWithReplaceOnPUTtrue POST /stores-replacing/replaceOrCreate',
|
||||
'replaceOrCreate(data:object):storeWithReplaceOnPUTtrue PUT /stores-replacing',
|
||||
'replaceById(id:any,data:object):storeWithReplaceOnPUTtrue POST /stores-replacing/:id/replace',
|
||||
'replaceById(id:any,data:object):storeWithReplaceOnPUTtrue PUT /stores-replacing/:id',
|
||||
'prototype.patchAttributes(data:object):storeWithReplaceOnPUTtrue PATCH /stores-replacing/:id',
|
||||
];
|
||||
|
||||
expect(methods).to.include.members(expectedMethods);
|
||||
});
|
||||
});
|
||||
|
||||
function formatReturns(m) {
|
||||
var returns = m.returns;
|
||||
if (!returns || returns.length === 0) {
|
||||
return '';
|
||||
}
|
||||
var type = returns[0].type;
|
||||
return type ? ':' + type : '';
|
||||
}
|
||||
|
||||
function formatMethod(m) {
|
||||
var arr = [];
|
||||
var endpoints = m.getEndpoints();
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
arr.push([
|
||||
m.name,
|
||||
'(',
|
||||
m.accepts.map(function(a) {
|
||||
return a.arg + ':' + a.type;
|
||||
}).join(','),
|
||||
')',
|
||||
formatReturns(m),
|
||||
' ',
|
||||
endpoints[i].verb,
|
||||
' ',
|
||||
endpoints[i].fullPath,
|
||||
].join(''));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function findClass(name) {
|
||||
return app.handler('rest').adapter
|
||||
.getClasses()
|
||||
.filter(function(c) {
|
||||
return c.name === name;
|
||||
})[0];
|
||||
}
|
||||
|
||||
function getFormattedMethodsExcludingRelations(methods) {
|
||||
return result = methods.filter(function(m) {
|
||||
return m.name.indexOf('__') === -1;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
})
|
||||
.reduce(function(p, c) {
|
||||
return p.concat(c);
|
||||
});
|
||||
}
|
||||
|
||||
function getFormattedScopeMethods(methods) {
|
||||
return result = methods.filter(function(m) {
|
||||
return m.name.indexOf('__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
})
|
||||
.reduce(function(p, c) {
|
||||
return p.concat(c);
|
||||
});
|
||||
}
|
||||
|
||||
function getFormattedPrototypeMethods(methods) {
|
||||
return result = methods.filter(function(m) {
|
||||
return m.name.indexOf('prototype.__') === 0;
|
||||
})
|
||||
.map(function(m) {
|
||||
return formatMethod(m);
|
||||
})
|
||||
.reduce(function(p, c) {
|
||||
return p.concat(c);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue