2017-03-21 11:24:11 +03:00
|
|
|
var should = require('should'),
|
|
|
|
sinon = require('sinon'),
|
|
|
|
_ = require('lodash'),
|
2015-06-22 23:11:35 +03:00
|
|
|
Promise = require('bluebird'),
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
ObjectId = require('bson-objectid'),
|
2017-12-14 05:01:23 +03:00
|
|
|
permissions = require('../../../server/services/permissions'),
|
2017-12-12 00:47:46 +03:00
|
|
|
common = require('../../../server/lib/common'),
|
2018-09-27 17:06:57 +03:00
|
|
|
apiUtils = require('../../../server/api/v0.1/utils'),
|
2017-03-21 11:24:11 +03:00
|
|
|
|
2015-06-27 21:09:25 +03:00
|
|
|
sandbox = sinon.sandbox.create();
|
2015-06-22 23:11:35 +03:00
|
|
|
|
2015-06-27 21:09:25 +03:00
|
|
|
describe('API Utils', function () {
|
2015-06-22 23:11:35 +03:00
|
|
|
afterEach(function () {
|
|
|
|
sandbox.restore();
|
|
|
|
});
|
|
|
|
|
2017-09-26 11:23:02 +03:00
|
|
|
it('exports', function () {
|
|
|
|
// @TODO reduce the number of methods that are here!
|
|
|
|
_.keys(apiUtils).should.eql([
|
|
|
|
'globalDefaultOptions',
|
|
|
|
'dataDefaultOptions',
|
|
|
|
'browseDefaultOptions',
|
|
|
|
'idDefaultOptions',
|
|
|
|
'validate',
|
|
|
|
'validateOptions',
|
|
|
|
'detectPublicContext',
|
|
|
|
'applyPublicPermissions',
|
|
|
|
'handlePublicPermissions',
|
|
|
|
'handlePermissions',
|
|
|
|
'trimAndLowerCase',
|
|
|
|
'prepareInclude',
|
|
|
|
'prepareFields',
|
|
|
|
'prepareFormats',
|
|
|
|
'convertOptions',
|
2017-12-14 23:25:51 +03:00
|
|
|
'checkObject'
|
2017-09-26 11:23:02 +03:00
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2015-07-01 21:17:56 +03:00
|
|
|
describe('Default Options', function () {
|
|
|
|
it('should provide a set of default options', function () {
|
|
|
|
apiUtils.globalDefaultOptions.should.eql(['context', 'include']);
|
2015-11-12 17:21:04 +03:00
|
|
|
apiUtils.browseDefaultOptions.should.eql(['page', 'limit', 'fields', 'filter', 'order', 'debug']);
|
2015-07-01 21:17:56 +03:00
|
|
|
apiUtils.dataDefaultOptions.should.eql(['data']);
|
|
|
|
apiUtils.idDefaultOptions.should.eql(['id']);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-06-22 23:11:35 +03:00
|
|
|
describe('validate', function () {
|
|
|
|
it('should create options when passed no args', function (done) {
|
|
|
|
apiUtils.validate()().then(function (options) {
|
|
|
|
options.should.eql({});
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should pick data attrs when passed them', function (done) {
|
2015-07-01 21:17:56 +03:00
|
|
|
apiUtils.validate('test', {attrs: ['id']})(
|
2015-06-22 23:11:35 +03:00
|
|
|
{id: 'test', status: 'all', uuid: 'other-test'}
|
|
|
|
).then(function (options) {
|
|
|
|
options.should.have.ownProperty('data');
|
|
|
|
options.data.should.have.ownProperty('id');
|
|
|
|
options.should.not.have.ownProperty('id');
|
|
|
|
options.data.id.should.eql('test');
|
|
|
|
|
2015-07-01 21:17:56 +03:00
|
|
|
options.data.should.not.have.ownProperty('status');
|
|
|
|
options.should.not.have.ownProperty('status');
|
|
|
|
|
|
|
|
options.should.not.have.ownProperty('uuid');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should pick data attrs & leave options if passed', function (done) {
|
|
|
|
apiUtils.validate('test', {attrs: ['id'], opts: ['status', 'uuid']})(
|
|
|
|
{id: 'test', status: 'all', uuid: 'ffecea44-393c-4273-b784-e1928975ecfb'}
|
|
|
|
).then(function (options) {
|
|
|
|
options.should.have.ownProperty('data');
|
|
|
|
options.data.should.have.ownProperty('id');
|
|
|
|
options.should.not.have.ownProperty('id');
|
|
|
|
options.data.id.should.eql('test');
|
|
|
|
|
2015-06-22 23:11:35 +03:00
|
|
|
options.data.should.not.have.ownProperty('status');
|
|
|
|
options.should.have.ownProperty('status');
|
|
|
|
options.status.should.eql('all');
|
|
|
|
|
|
|
|
options.should.have.ownProperty('uuid');
|
2015-07-01 21:17:56 +03:00
|
|
|
options.uuid.should.eql('ffecea44-393c-4273-b784-e1928975ecfb');
|
2015-06-22 23:11:35 +03:00
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should check data if an object is passed', function (done) {
|
|
|
|
var object = {test: [{id: 1}]},
|
|
|
|
checkObjectStub = sandbox.stub(apiUtils, 'checkObject').returns(Promise.resolve(object));
|
|
|
|
|
|
|
|
apiUtils.validate('test')(object, {}).then(function (options) {
|
2016-02-08 00:27:01 +03:00
|
|
|
checkObjectStub.calledOnce.should.be.true();
|
|
|
|
checkObjectStub.calledWith(object, 'test').should.be.true();
|
2015-06-22 23:11:35 +03:00
|
|
|
options.should.have.ownProperty('data');
|
|
|
|
options.data.should.have.ownProperty('test');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle options being undefined', function (done) {
|
|
|
|
apiUtils.validate()(undefined).then(function (options) {
|
|
|
|
options.should.eql({});
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle options being undefined when provided with object', function (done) {
|
|
|
|
var object = {test: [{id: 1}]},
|
|
|
|
checkObjectStub = sandbox.stub(apiUtils, 'checkObject').returns(Promise.resolve(object));
|
|
|
|
|
|
|
|
apiUtils.validate('test')(object, undefined).then(function (options) {
|
2016-02-08 00:27:01 +03:00
|
|
|
checkObjectStub.calledOnce.should.be.true();
|
|
|
|
checkObjectStub.calledWith(object, 'test').should.be.true();
|
2015-06-22 23:11:35 +03:00
|
|
|
options.should.have.ownProperty('data');
|
|
|
|
options.data.should.have.ownProperty('test');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
2015-07-01 21:17:56 +03:00
|
|
|
|
|
|
|
it('should remove unknown options', function (done) {
|
|
|
|
apiUtils.validate('test')({magic: 'stuff', rubbish: 'stuff'}).then(function (options) {
|
|
|
|
options.should.not.have.ownProperty('data');
|
|
|
|
options.should.not.have.ownProperty('rubbish');
|
|
|
|
options.should.not.have.ownProperty('magic');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should always allow context & include options', function (done) {
|
|
|
|
apiUtils.validate('test')({context: 'stuff', include: 'stuff'}).then(function (options) {
|
|
|
|
options.should.not.have.ownProperty('data');
|
|
|
|
options.should.have.ownProperty('context');
|
|
|
|
options.context.should.eql('stuff');
|
|
|
|
options.should.have.ownProperty('include');
|
|
|
|
options.include.should.eql('stuff');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should allow page & limit options when browseDefaultOptions passed', function (done) {
|
|
|
|
apiUtils.validate('test', {opts: apiUtils.browseDefaultOptions})(
|
|
|
|
{context: 'stuff', include: 'stuff', page: 1, limit: 5}
|
|
|
|
).then(function (options) {
|
|
|
|
options.should.not.have.ownProperty('data');
|
|
|
|
options.should.have.ownProperty('context');
|
|
|
|
options.context.should.eql('stuff');
|
|
|
|
options.should.have.ownProperty('include');
|
|
|
|
options.include.should.eql('stuff');
|
|
|
|
options.should.have.ownProperty('page');
|
|
|
|
options.page.should.eql(1);
|
|
|
|
options.should.have.ownProperty('limit');
|
|
|
|
options.limit.should.eql(5);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should allow idDefaultOptions when passed', function (done) {
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
var id = ObjectId.generate();
|
|
|
|
|
2015-07-01 21:17:56 +03:00
|
|
|
apiUtils.validate('test', {opts: apiUtils.idDefaultOptions})(
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
{id: id, context: 'stuff'}
|
2015-07-01 21:17:56 +03:00
|
|
|
).then(function (options) {
|
|
|
|
options.should.not.have.ownProperty('data');
|
|
|
|
options.should.not.have.ownProperty('include');
|
|
|
|
options.should.not.have.ownProperty('page');
|
|
|
|
options.should.not.have.ownProperty('limit');
|
|
|
|
|
|
|
|
options.should.have.ownProperty('context');
|
|
|
|
options.context.should.eql('stuff');
|
|
|
|
options.should.have.ownProperty('id');
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
options.id.should.eql(id);
|
2015-07-01 21:17:56 +03:00
|
|
|
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
2016-05-19 14:49:22 +03:00
|
|
|
it('should reject if limit is invalid', function (done) {
|
2015-07-01 21:17:56 +03:00
|
|
|
apiUtils.validate('test', {opts: apiUtils.browseDefaultOptions})(
|
2016-05-19 14:49:22 +03:00
|
|
|
{limit: 'none'}
|
|
|
|
).then(function () {
|
|
|
|
done(new Error('Should have thrown a validation error'));
|
|
|
|
}).catch(function (err) {
|
|
|
|
err.should.have.property('errorType', 'ValidationError');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should reject if from is invalid', function (done) {
|
|
|
|
apiUtils.validate('test', {opts: ['from']})(
|
|
|
|
{from: true}
|
2015-07-01 21:17:56 +03:00
|
|
|
).then(function () {
|
|
|
|
done(new Error('Should have thrown a validation error'));
|
|
|
|
}).catch(function (err) {
|
2015-09-22 19:38:30 +03:00
|
|
|
err.should.have.property('errorType', 'ValidationError');
|
2015-07-01 21:17:56 +03:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('validateOptions', function () {
|
|
|
|
var valid, invalid;
|
|
|
|
|
|
|
|
function check(key, valid, invalid) {
|
|
|
|
_.each(valid, function (value) {
|
|
|
|
var options = {};
|
|
|
|
options[key] = value;
|
|
|
|
apiUtils.validateOptions(options).should.eql([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
_.each(invalid, function (value) {
|
|
|
|
var options = {}, errors;
|
|
|
|
options[key] = value;
|
|
|
|
|
|
|
|
errors = apiUtils.validateOptions(options);
|
2016-02-08 00:27:01 +03:00
|
|
|
errors.should.be.an.Array().and.have.lengthOf(1);
|
2017-11-28 21:41:16 +03:00
|
|
|
errors[0].errorType.should.eql('ValidationError');
|
2015-07-01 21:17:56 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
it('can validate `id`', function () {
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
valid = [ObjectId.generate(), '1', 1];
|
|
|
|
invalid = ['test', 'de305d54', 300, '304'];
|
2015-07-01 21:17:56 +03:00
|
|
|
|
|
|
|
check('id', valid, invalid);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can validate `uuid`', function () {
|
|
|
|
valid = ['de305d54-75b4-431b-adb2-eb6b9e546014'];
|
|
|
|
invalid = ['de305d54-75b4-431b-adb2'];
|
|
|
|
|
|
|
|
check('uuid', valid, invalid);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can validate `page`', function () {
|
|
|
|
valid = [1, '1', 304, '304'];
|
|
|
|
invalid = ['me', 'test', 'de305d54', -1, '-1'];
|
|
|
|
|
|
|
|
check('page', valid, invalid);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can validate `limit`', function () {
|
|
|
|
valid = [1, '1', 304, '304', 'all'];
|
|
|
|
invalid = ['me', 'test', 'de305d54', -1, '-1'];
|
|
|
|
|
|
|
|
check('limit', valid, invalid);
|
|
|
|
});
|
|
|
|
|
2015-09-04 20:29:15 +03:00
|
|
|
it('can validate `slug` or `status` or `author` etc as a-z, 0-9, - and _', function () {
|
|
|
|
valid = ['hello-world', 'hello', '1-2-3', 1, '-1', -1, 'hello_world'];
|
|
|
|
invalid = ['hello~world', '!things', '?other-things', 'thing"', '`ticks`'];
|
2015-07-01 21:17:56 +03:00
|
|
|
|
|
|
|
check('slug', valid, invalid);
|
|
|
|
check('status', valid, invalid);
|
|
|
|
check('author', valid, invalid);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('gives no errors for `context`, `include` and `data`', function () {
|
|
|
|
apiUtils.validateOptions({
|
|
|
|
context: {user: 1},
|
|
|
|
include: '"super,@random!,string?and',
|
|
|
|
data: {object: 'thing'}
|
|
|
|
}).should.eql([]);
|
|
|
|
});
|
2015-06-22 23:11:35 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('prepareInclude', function () {
|
|
|
|
it('should handle empty items', function () {
|
|
|
|
apiUtils.prepareInclude('', []).should.eql([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be empty if there are no allowed includes', function () {
|
|
|
|
apiUtils.prepareInclude('a,b,c', []).should.eql([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return correct includes', function () {
|
|
|
|
apiUtils.prepareInclude('a,b,c', ['a']).should.eql(['a']);
|
|
|
|
apiUtils.prepareInclude('a,b,c', ['a', 'c']).should.eql(['a', 'c']);
|
|
|
|
apiUtils.prepareInclude('a,b,c', ['a', 'd']).should.eql(['a']);
|
|
|
|
apiUtils.prepareInclude('a,b,c', ['d']).should.eql([]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('convertOptions', function () {
|
|
|
|
it('should not call prepareInclude if there is no include option', function () {
|
|
|
|
var prepareIncludeStub = sandbox.stub(apiUtils, 'prepareInclude');
|
|
|
|
apiUtils.convertOptions(['a', 'b', 'c'])({}).should.eql({});
|
2016-02-08 00:27:01 +03:00
|
|
|
prepareIncludeStub.called.should.be.false();
|
2015-06-22 23:11:35 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should pass options.include to prepareInclude if provided', function () {
|
|
|
|
var expectedResult = ['a', 'b'],
|
|
|
|
prepareIncludeStub = sandbox.stub(apiUtils, 'prepareInclude').returns(expectedResult),
|
|
|
|
allowed = ['a', 'b', 'c'],
|
|
|
|
options = {include: 'a,b'},
|
|
|
|
actualResult;
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
|
2015-06-22 23:11:35 +03:00
|
|
|
actualResult = apiUtils.convertOptions(allowed)(_.clone(options));
|
|
|
|
|
2016-02-08 00:27:01 +03:00
|
|
|
prepareIncludeStub.calledOnce.should.be.true();
|
|
|
|
prepareIncludeStub.calledWith(options.include, allowed).should.be.true();
|
2015-06-22 23:11:35 +03:00
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
actualResult.should.have.hasOwnProperty('withRelated');
|
|
|
|
actualResult.withRelated.should.be.an.Array();
|
|
|
|
actualResult.withRelated.should.eql(expectedResult);
|
2015-06-22 23:11:35 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('checkObject', function () {
|
|
|
|
it('throws an error if the object is empty', function (done) {
|
|
|
|
apiUtils.checkObject({}, 'test').then(function () {
|
|
|
|
done('This should have thrown an error');
|
|
|
|
}).catch(function (error) {
|
|
|
|
should.exist(error);
|
|
|
|
error.errorType.should.eql('BadRequestError');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws an error if the object key is empty', function (done) {
|
|
|
|
apiUtils.checkObject({test: []}, 'test').then(function () {
|
|
|
|
done('This should have thrown an error');
|
|
|
|
}).catch(function (error) {
|
|
|
|
should.exist(error);
|
|
|
|
error.errorType.should.eql('BadRequestError');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws an error if the object key is array with empty object', function (done) {
|
|
|
|
apiUtils.checkObject({test: [{}]}, 'test').then(function () {
|
|
|
|
done('This should have thrown an error');
|
|
|
|
}).catch(function (error) {
|
|
|
|
should.exist(error);
|
|
|
|
error.errorType.should.eql('BadRequestError');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('passed through a simple, correct object', function (done) {
|
|
|
|
var object = {test: [{id: 1}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test').then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.have.ownProperty('test');
|
|
|
|
object.should.eql(data);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
2018-04-05 16:59:52 +03:00
|
|
|
it('passed through a simple, correct object', function (done) {
|
|
|
|
var object = {test: [{id: 1}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test').then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.have.ownProperty('test');
|
|
|
|
object.should.eql(data);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
2018-03-27 17:16:15 +03:00
|
|
|
it('[DEPRECATED] should do author_id to author conversion for posts', function (done) {
|
2015-06-22 23:11:35 +03:00
|
|
|
var object = {posts: [{id: 1, author: 4}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts').then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.have.ownProperty('posts');
|
|
|
|
data.should.not.eql(object);
|
2016-02-08 00:27:01 +03:00
|
|
|
data.posts.should.be.an.Array();
|
2015-06-22 23:11:35 +03:00
|
|
|
data.posts[0].should.have.ownProperty('author_id');
|
|
|
|
data.posts[0].should.not.have.ownProperty('author');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
2018-03-27 17:16:15 +03:00
|
|
|
it('[DEPRECATED] should not do author_id to author conversion for posts if not needed', function (done) {
|
2015-06-22 23:11:35 +03:00
|
|
|
var object = {posts: [{id: 1, author_id: 4}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts').then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.have.ownProperty('posts');
|
|
|
|
data.should.eql(object);
|
2016-02-08 00:27:01 +03:00
|
|
|
data.posts.should.be.an.Array();
|
2015-06-22 23:11:35 +03:00
|
|
|
data.posts[0].should.have.ownProperty('author_id');
|
|
|
|
data.posts[0].should.not.have.ownProperty('author');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw error if invalid editId if provided', function (done) {
|
|
|
|
var object = {test: [{id: 1}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', 3).then(function () {
|
|
|
|
done('This should have thrown an error');
|
|
|
|
}).catch(function (error) {
|
|
|
|
should.exist(error);
|
|
|
|
error.errorType.should.eql('BadRequestError');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore undefined editId', function (done) {
|
|
|
|
var object = {test: [{id: 1}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', undefined).then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.eql(object);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore editId if object has no id', function (done) {
|
|
|
|
var object = {test: [{uuid: 1}]};
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', 3).then(function (data) {
|
|
|
|
should.exist(data);
|
|
|
|
data.should.eql(object);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
2016-05-08 10:16:24 +03:00
|
|
|
|
|
|
|
it('will delete null values from object', function (done) {
|
|
|
|
var object = {test: [{id: 1, key: null}]};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test').then(function (data) {
|
|
|
|
should.not.exist(data.test[0].key);
|
|
|
|
should.exist(data.test[0].id);
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will not break if the expected object is a string', function (done) {
|
|
|
|
var object = {test: ['something']};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test').then(function (data) {
|
|
|
|
data.test[0].should.eql('something');
|
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
2018-03-27 17:16:15 +03:00
|
|
|
|
2018-04-05 16:59:52 +03:00
|
|
|
describe('post.tags structure', function () {
|
|
|
|
it('post.tags is empty', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
tags: []
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function (object) {
|
|
|
|
object.posts[0].tags.length.should.eql(0);
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.tags contains `parent`', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
tags: [{id: 'objectid', parent: null}]
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function (object) {
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent').should.be.false();
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent_id').should.be.true();
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.tags contains `parent_id`', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
tags: [{id: 'objectid', parent_id: null}]
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function (object) {
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent').should.be.false();
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent_id').should.be.true();
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.tags contains no parent', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
tags: [{id: 'objectid'}]
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function (object) {
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent').should.be.false();
|
|
|
|
object.posts[0].tags[0].hasOwnProperty('parent_id').should.be.false();
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('post.authors structure', function () {
|
2018-03-27 17:16:15 +03:00
|
|
|
it('post.authors is not present', function (done) {
|
|
|
|
var object = {posts: [{id: 1}]};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function () {
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.authors is no array', function (done) {
|
|
|
|
var object = {posts: [{id: 1, authors: null}]};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function () {
|
|
|
|
"Test should fail".should.eql(false);
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
(err instanceof common.errors.BadRequestError).should.be.true;
|
|
|
|
err.message.should.eql('No valid object structure provided for: posts[*].authors');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.authors is empty', function (done) {
|
|
|
|
var object = {posts: [{id: 1, authors: []}]};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function () {
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.authors contains id property', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
authors: [{id: 'objectid', name: 'Kate'}, {id: 'objectid', name: 'Steffen'}]
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function () {
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('post.authors does not contain id property', function (done) {
|
|
|
|
var object = {posts: [{id: 1, authors: [{id: 'objectid', name: 'Kate'}, {name: 'Steffen'}]}]};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function () {
|
|
|
|
"Test should fail".should.eql(false);
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
(err instanceof common.errors.BadRequestError).should.be.true;
|
|
|
|
err.message.should.eql('No valid object structure provided for: posts[*].authors');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2018-04-05 16:59:52 +03:00
|
|
|
|
|
|
|
it('post.authors contains nested relations', function (done) {
|
|
|
|
var object = {
|
|
|
|
posts: [{
|
|
|
|
id: 1,
|
|
|
|
authors: [{id: 'objectid', name: 'Kate', roles: [{id: 'something'}], permissions: []}]
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts')
|
|
|
|
.then(function (object) {
|
|
|
|
should.not.exist(object.posts[0].authors[0].roles);
|
|
|
|
should.not.exist(object.posts[0].authors[0].permissions);
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
2018-03-27 17:16:15 +03:00
|
|
|
});
|
2015-06-22 23:11:35 +03:00
|
|
|
});
|
|
|
|
|
2015-06-27 21:09:25 +03:00
|
|
|
describe('isPublicContext', function () {
|
|
|
|
it('should call out to permissions', function () {
|
|
|
|
var permsStub = sandbox.stub(permissions, 'parseContext').returns({public: true});
|
2016-02-08 00:27:01 +03:00
|
|
|
apiUtils.detectPublicContext({context: 'test'}).should.be.true();
|
|
|
|
permsStub.called.should.be.true();
|
|
|
|
permsStub.calledWith('test').should.be.true();
|
2015-06-27 21:09:25 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('applyPublicPermissions', function () {
|
|
|
|
it('should call out to permissions', function () {
|
|
|
|
var permsStub = sandbox.stub(permissions, 'applyPublicRules');
|
|
|
|
apiUtils.applyPublicPermissions('test', {});
|
2016-02-08 00:27:01 +03:00
|
|
|
permsStub.called.should.be.true();
|
|
|
|
permsStub.calledWith('test', {}).should.be.true();
|
2015-06-27 21:09:25 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('handlePublicPermissions', function () {
|
|
|
|
it('should return empty options if passed empty options', function (done) {
|
|
|
|
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
2016-04-29 13:58:40 +03:00
|
|
|
options.should.eql({context: {app: null, external: false, internal: false, public: true, user: null}});
|
2015-06-27 21:09:25 +03:00
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should treat no context as public', function (done) {
|
|
|
|
var aPPStub = sandbox.stub(apiUtils, 'applyPublicPermissions').returns(Promise.resolve({}));
|
|
|
|
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
|
|
|
aPPStub.calledOnce.should.eql(true);
|
2016-04-29 13:58:40 +03:00
|
|
|
options.should.eql({context: {app: null, external: false, internal: false, public: true, user: null}});
|
2015-06-27 21:09:25 +03:00
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should treat user context as NOT public', function (done) {
|
|
|
|
var cTMethodStub = {
|
|
|
|
test: {
|
|
|
|
test: sandbox.stub().returns(Promise.resolve())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cTStub = sandbox.stub(permissions, 'canThis').returns(cTMethodStub);
|
|
|
|
|
|
|
|
apiUtils.handlePublicPermissions('tests', 'test')({context: {user: 1}}).then(function (options) {
|
|
|
|
cTStub.calledOnce.should.eql(true);
|
|
|
|
cTMethodStub.test.test.calledOnce.should.eql(true);
|
2016-04-29 13:58:40 +03:00
|
|
|
options.should.eql({context: {app: null, external: false, internal: false, public: false, user: 1}});
|
2015-06-27 21:09:25 +03:00
|
|
|
done();
|
|
|
|
}).catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw a permissions error if permission is not granted', function (done) {
|
|
|
|
var cTMethodStub = {
|
|
|
|
test: {
|
2017-12-12 00:47:46 +03:00
|
|
|
test: sandbox.stub().returns(Promise.reject(new common.errors.NoPermissionError()))
|
2015-06-27 21:09:25 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
cTStub = sandbox.stub(permissions, 'canThis').returns(cTMethodStub);
|
|
|
|
|
|
|
|
apiUtils.handlePublicPermissions('tests', 'test')({context: {user: 1}}).then(function () {
|
|
|
|
done(new Error('should throw error when no permissions'));
|
|
|
|
}).catch(function (err) {
|
|
|
|
cTStub.calledOnce.should.eql(true);
|
|
|
|
cTMethodStub.test.test.calledOnce.should.eql(true);
|
|
|
|
err.errorType.should.eql('NoPermissionError');
|
|
|
|
done();
|
2016-10-04 18:33:43 +03:00
|
|
|
});
|
2015-06-27 21:09:25 +03:00
|
|
|
});
|
|
|
|
});
|
2017-09-26 11:23:02 +03:00
|
|
|
|
|
|
|
describe('handlePermissions', function () {
|
|
|
|
it('should require a docName', function () {
|
|
|
|
apiUtils.handlePermissions.should.throwError();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return a function', function () {
|
|
|
|
apiUtils.handlePermissions('test').should.be.a.Function();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle an unknown rejection', function (done) {
|
2018-02-21 18:59:48 +03:00
|
|
|
var testStub = sandbox.stub().returns(new Promise.reject(new Error('not found'))),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 11:23:02 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing');
|
|
|
|
|
|
|
|
permsFunc({})
|
|
|
|
.then(function () {
|
|
|
|
done(new Error('Should have thrown an error'));
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
|
|
|
err.errorType.should.eql('InternalServerError');
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle a NoPermissions rejection', function (done) {
|
2017-12-12 00:47:46 +03:00
|
|
|
var testStub = sandbox.stub().returns(Promise.reject(new common.errors.NoPermissionError())),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 11:23:02 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing');
|
|
|
|
|
|
|
|
permsFunc({})
|
|
|
|
.then(function () {
|
|
|
|
done(new Error('Should have thrown an error'));
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
|
|
|
|
|
|
|
err.errorType.should.eql('NoPermissionError');
|
|
|
|
err.message.should.match(/testing/);
|
|
|
|
err.message.should.match(/tests/);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle success', function (done) {
|
|
|
|
var testStub = sandbox.stub().returns(new Promise.resolve()),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 11:23:02 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing'),
|
|
|
|
testObj = {foo: 'bar', id: 5};
|
|
|
|
|
|
|
|
permsFunc(testObj)
|
|
|
|
.then(function (res) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
2017-09-26 19:06:14 +03:00
|
|
|
testStub.firstCall.args.length.should.eql(2);
|
2017-09-26 11:23:02 +03:00
|
|
|
testStub.firstCall.args[0].should.eql(5);
|
2017-09-26 19:06:14 +03:00
|
|
|
testStub.firstCall.args[1].should.eql({});
|
|
|
|
|
|
|
|
res.should.eql(testObj);
|
|
|
|
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore unsafe attrs if none are provided', function (done) {
|
|
|
|
var testStub = sandbox.stub().returns(new Promise.resolve()),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 19:06:14 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing', ['foo']),
|
|
|
|
testObj = {data: {tests: [{}]}, id: 5};
|
|
|
|
|
|
|
|
permsFunc(testObj)
|
|
|
|
.then(function (res) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
|
|
|
testStub.firstCall.args.length.should.eql(2);
|
|
|
|
testStub.firstCall.args[0].should.eql(5);
|
|
|
|
testStub.firstCall.args[1].should.eql({});
|
|
|
|
|
|
|
|
res.should.eql(testObj);
|
|
|
|
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore unsafe attrs if they are provided but not present', function (done) {
|
|
|
|
var testStub = sandbox.stub().returns(new Promise.resolve()),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 19:06:14 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing', ['foo']),
|
|
|
|
testObj = {foo: 'bar', id: 5};
|
|
|
|
|
|
|
|
permsFunc(testObj)
|
|
|
|
.then(function (res) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
|
|
|
testStub.firstCall.args.length.should.eql(2);
|
|
|
|
testStub.firstCall.args[0].should.eql(5);
|
|
|
|
testStub.firstCall.args[1].should.eql({});
|
|
|
|
|
|
|
|
res.should.eql(testObj);
|
|
|
|
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should pass through unsafe attrs if they DO exist', function (done) {
|
|
|
|
var testStub = sandbox.stub().returns(new Promise.resolve()),
|
2017-11-28 20:19:23 +03:00
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').callsFake(function () {
|
2017-09-26 19:06:14 +03:00
|
|
|
return {
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing', ['foo']),
|
|
|
|
testObj = {data: {tests: [{foo: 'bar'}]}, id: 5};
|
|
|
|
|
|
|
|
permsFunc(testObj)
|
|
|
|
.then(function (res) {
|
|
|
|
permsStub.callCount.should.eql(1);
|
|
|
|
testStub.callCount.should.eql(1);
|
|
|
|
testStub.firstCall.args.length.should.eql(2);
|
|
|
|
testStub.firstCall.args[0].should.eql(5);
|
|
|
|
testStub.firstCall.args[1].should.eql({foo: 'bar'});
|
2017-09-26 11:23:02 +03:00
|
|
|
|
|
|
|
res.should.eql(testObj);
|
|
|
|
|
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch(done);
|
|
|
|
});
|
2018-02-07 12:46:22 +03:00
|
|
|
|
|
|
|
it('should strip excludedAttrs from data if permissions function returns them', function () {
|
|
|
|
var testStub = sandbox.stub().resolves({excludedAttrs: ['foo']}),
|
|
|
|
permsStub = sandbox.stub(permissions, 'canThis').returns({
|
|
|
|
testing: {
|
|
|
|
test: testStub
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
permsFunc = apiUtils.handlePermissions('tests', 'testing'),
|
|
|
|
testObj = {data: {tests: [{id: 5, name: 'testing', foo: 'bar'}]}, id: 5};
|
|
|
|
|
|
|
|
return permsFunc(testObj).then(function (res) {
|
|
|
|
permsStub.calledOnce.should.be.true();
|
|
|
|
testStub.calledOnce.should.be.true();
|
|
|
|
testStub.calledWithExactly(5, {}).should.be.true();
|
|
|
|
|
|
|
|
should(res).deepEqual({
|
|
|
|
data: {
|
|
|
|
tests: [{id: 5, name: 'testing'}]
|
|
|
|
},
|
|
|
|
id: 5
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2017-09-26 11:23:02 +03:00
|
|
|
});
|
2015-06-22 23:11:35 +03:00
|
|
|
});
|