mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Merge pull request #5978 from ErisDS/filter-param
Add the filter parameter
This commit is contained in:
commit
28871d3f4d
11 changed files with 998 additions and 37 deletions
|
@ -23,7 +23,7 @@ utils = {
|
||||||
// ### Manual Default Options
|
// ### Manual Default Options
|
||||||
// These must be provided by the endpoint
|
// These must be provided by the endpoint
|
||||||
// browseDefaultOptions - valid for all browse api endpoints
|
// browseDefaultOptions - valid for all browse api endpoints
|
||||||
browseDefaultOptions: ['page', 'limit', 'fields'],
|
browseDefaultOptions: ['page', 'limit', 'fields', 'filter'],
|
||||||
// idDefaultOptions - valid whenever an id is valid
|
// idDefaultOptions - valid whenever an id is valid
|
||||||
idDefaultOptions: ['id'],
|
idDefaultOptions: ['id'],
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ utils = {
|
||||||
name: {}
|
name: {}
|
||||||
},
|
},
|
||||||
// these values are sanitised/validated separately
|
// these values are sanitised/validated separately
|
||||||
noValidation = ['data', 'context', 'include'],
|
noValidation = ['data', 'context', 'include', 'filter'],
|
||||||
errors = [];
|
errors = [];
|
||||||
|
|
||||||
_.each(options, function (value, key) {
|
_.each(options, function (value, key) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ var _ = require('lodash'),
|
||||||
validation = require('../../data/validation'),
|
validation = require('../../data/validation'),
|
||||||
baseUtils = require('./utils'),
|
baseUtils = require('./utils'),
|
||||||
pagination = require('./pagination'),
|
pagination = require('./pagination'),
|
||||||
|
gql = require('ghost-gql'),
|
||||||
|
|
||||||
ghostBookshelf;
|
ghostBookshelf;
|
||||||
|
|
||||||
|
@ -283,14 +284,24 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefetch filter objects
|
// Prefetch filter objects
|
||||||
return Promise.all(baseUtils.filtering.preFetch(filterObjects)).then(function doQuery() {
|
return Promise.all(baseUtils.oldFiltering.preFetch(filterObjects)).then(function doQuery() {
|
||||||
// If there are `where` conditionals specified, add those to the query.
|
// If there are `where` conditionals specified, add those to the query.
|
||||||
if (options.where) {
|
if (options.where) {
|
||||||
itemCollection.query('where', options.where);
|
itemCollection.query('where', options.where);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply FILTER
|
||||||
|
if (options.filter) {
|
||||||
|
options.filter = gql.parse(options.filter);
|
||||||
|
itemCollection.query(function (qb) {
|
||||||
|
gql.knexify(qb, options.filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
baseUtils.processGQLResult(itemCollection, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup filter joins / queries
|
// Setup filter joins / queries
|
||||||
baseUtils.filtering.query(filterObjects, itemCollection);
|
baseUtils.oldFiltering.query(filterObjects, itemCollection);
|
||||||
|
|
||||||
// Handle related objects
|
// Handle related objects
|
||||||
// TODO: this should just be done for all methods @ the API level
|
// TODO: this should just be done for all methods @ the API level
|
||||||
|
@ -303,7 +314,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||||
data[tableName] = response.collection.toJSON(options);
|
data[tableName] = response.collection.toJSON(options);
|
||||||
data.meta = {pagination: response.pagination};
|
data.meta = {pagination: response.pagination};
|
||||||
|
|
||||||
return baseUtils.filtering.formatResponse(filterObjects, options, data);
|
return baseUtils.oldFiltering.formatResponse(filterObjects, options, data);
|
||||||
});
|
});
|
||||||
}).catch(errors.logAndThrowError);
|
}).catch(errors.logAndThrowError);
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,10 +48,10 @@ paginationUtils = {
|
||||||
/**
|
/**
|
||||||
* ### Query
|
* ### Query
|
||||||
* Apply the necessary parameters to paginate the query
|
* Apply the necessary parameters to paginate the query
|
||||||
* @param {Bookshelf.Model, Bookshelf.Collection} itemCollection
|
* @param {Bookshelf.Model|Bookshelf.Collection} itemCollection
|
||||||
* @param {options} options
|
* @param {options} options
|
||||||
*/
|
*/
|
||||||
query: function query(itemCollection, options) {
|
addLimitAndOffset: function addLimitAndOffset(itemCollection, options) {
|
||||||
if (_.isNumber(options.limit)) {
|
if (_.isNumber(options.limit)) {
|
||||||
itemCollection
|
itemCollection
|
||||||
.query('limit', options.limit)
|
.query('limit', options.limit)
|
||||||
|
@ -151,7 +151,10 @@ pagination = function pagination(bookshelf) {
|
||||||
// Add any where or join clauses which need to be included with the aggregate query
|
// Add any where or join clauses which need to be included with the aggregate query
|
||||||
|
|
||||||
// Clone the base query & set up a promise to get the count of total items in the full set
|
// Clone the base query & set up a promise to get the count of total items in the full set
|
||||||
countPromise = this.query().clone().count(tableName + '.' + idAttribute + ' as aggregate');
|
// Due to lack of support for count distinct, this is pretty complex.
|
||||||
|
countPromise = this.query().clone().select(
|
||||||
|
bookshelf.knex.raw('count(distinct ' + tableName + '.' + idAttribute + ') as aggregate')
|
||||||
|
);
|
||||||
|
|
||||||
// Clone the base query into our collection
|
// Clone the base query into our collection
|
||||||
collection._knex = this.query().clone();
|
collection._knex = this.query().clone();
|
||||||
|
@ -160,7 +163,7 @@ pagination = function pagination(bookshelf) {
|
||||||
// Add any where or join clauses which need to NOT be included with the aggregate query
|
// Add any where or join clauses which need to NOT be included with the aggregate query
|
||||||
|
|
||||||
// Setup the pagination parameters so that we return the correct items from the set
|
// Setup the pagination parameters so that we return the correct items from the set
|
||||||
paginationUtils.query(collection, options);
|
paginationUtils.addLimitAndOffset(collection, options);
|
||||||
|
|
||||||
// Apply ordering options if they are present
|
// Apply ordering options if they are present
|
||||||
if (options.order && !_.isEmpty(options.order)) {
|
if (options.order && !_.isEmpty(options.order)) {
|
||||||
|
@ -169,6 +172,12 @@ pagination = function pagination(bookshelf) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.groups && !_.isEmpty(options.groups)) {
|
||||||
|
_.each(options.groups, function (group) {
|
||||||
|
collection.query('groupBy', group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply count options if they are present
|
// Apply count options if they are present
|
||||||
baseUtils.collectionQuery.count(collection, options);
|
baseUtils.collectionQuery.count(collection, options);
|
||||||
|
|
||||||
|
@ -195,7 +204,7 @@ pagination = function pagination(bookshelf) {
|
||||||
// Format the collection & count result into `{collection: [], pagination: {}}`
|
// Format the collection & count result into `{collection: [], pagination: {}}`
|
||||||
return {
|
return {
|
||||||
collection: results[0],
|
collection: results[0],
|
||||||
pagination: paginationUtils.formatResponse(results[1][0].aggregate, options)
|
pagination: paginationUtils.formatResponse(results[1][0] ? results[1][0].aggregate : 0, options)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
collectionQuery,
|
collectionQuery,
|
||||||
|
processGQLResult,
|
||||||
filtering,
|
filtering,
|
||||||
addPostCount,
|
addPostCount,
|
||||||
tagUpdate;
|
tagUpdate;
|
||||||
|
@ -25,6 +26,48 @@ collectionQuery = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
processGQLResult = function processGQLResult(itemCollection, options) {
|
||||||
|
var joinTables = options.filter.joins,
|
||||||
|
tagsHasIn = false;
|
||||||
|
|
||||||
|
if (joinTables && joinTables.indexOf('tags') > -1) {
|
||||||
|
// We need to use leftOuterJoin to insure we still include posts which don't have tags in the result
|
||||||
|
// The where clause should restrict which items are returned
|
||||||
|
itemCollection
|
||||||
|
.query('leftOuterJoin', 'posts_tags', 'posts_tags.post_id', '=', 'posts.id')
|
||||||
|
.query('leftOuterJoin', 'tags', 'posts_tags.tag_id', '=', 'tags.id');
|
||||||
|
|
||||||
|
// The order override should ONLY happen if we are doing an "IN" query
|
||||||
|
// TODO move the order handling to the query building that is currently inside pagination
|
||||||
|
// TODO make the order handling in pagination handle orderByRaw
|
||||||
|
// TODO extend this handling to all joins
|
||||||
|
_.each(options.filter.statements, function (statement) {
|
||||||
|
if (statement.op === 'IN' && statement.prop.match(/tags/)) {
|
||||||
|
tagsHasIn = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tagsHasIn) {
|
||||||
|
// TODO make this count the number of MATCHING tags, not just the number of tags
|
||||||
|
itemCollection.query('orderByRaw', 'count(tags.id) DESC');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to add a group by to counter the double left outer join
|
||||||
|
// TODO improve on th group by handling
|
||||||
|
options.groups = options.groups || [];
|
||||||
|
options.groups.push('posts.id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joinTables && joinTables.indexOf('author') > -1) {
|
||||||
|
itemCollection
|
||||||
|
.query('join', 'users as author', 'author.id', '=', 'posts.author_id');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of this can be removed once the filter parameter is in place
|
||||||
|
* And the current filtering methods are removed
|
||||||
|
*/
|
||||||
filtering = {
|
filtering = {
|
||||||
preFetch: function preFetch(filterObjects) {
|
preFetch: function preFetch(filterObjects) {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
|
@ -129,7 +172,8 @@ tagUpdate = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.filtering = filtering;
|
module.exports.oldFiltering = filtering;
|
||||||
|
module.exports.processGQLResult = processGQLResult;
|
||||||
module.exports.collectionQuery = collectionQuery;
|
module.exports.collectionQuery = collectionQuery;
|
||||||
module.exports.addPostCount = addPostCount;
|
module.exports.addPostCount = addPostCount;
|
||||||
module.exports.tagUpdate = tagUpdate;
|
module.exports.tagUpdate = tagUpdate;
|
||||||
|
|
|
@ -366,7 +366,7 @@ Post = ghostBookshelf.Model.extend({
|
||||||
if (!_.isBoolean(options.staticPages)) {
|
if (!_.isBoolean(options.staticPages)) {
|
||||||
options.staticPages = _.contains(['true', '1'], options.staticPages);
|
options.staticPages = _.contains(['true', '1'], options.staticPages);
|
||||||
}
|
}
|
||||||
options.where.page = options.staticPages;
|
options.where['posts.page'] = options.staticPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.has(options, 'featured')) {
|
if (_.has(options, 'featured')) {
|
||||||
|
@ -374,7 +374,7 @@ Post = ghostBookshelf.Model.extend({
|
||||||
if (!_.isBoolean(options.featured)) {
|
if (!_.isBoolean(options.featured)) {
|
||||||
options.featured = _.contains(['true', '1'], options.featured);
|
options.featured = _.contains(['true', '1'], options.featured);
|
||||||
}
|
}
|
||||||
options.where.featured = options.featured;
|
options.where['posts.featured'] = options.featured;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unless `all` is passed as an option, filter on
|
// Unless `all` is passed as an option, filter on
|
||||||
|
@ -382,7 +382,7 @@ Post = ghostBookshelf.Model.extend({
|
||||||
if (options.status !== 'all') {
|
if (options.status !== 'all') {
|
||||||
// make sure that status is valid
|
// make sure that status is valid
|
||||||
options.status = _.contains(['published', 'draft'], options.status) ? options.status : 'published';
|
options.status = _.contains(['published', 'draft'], options.status) ? options.status : 'published';
|
||||||
options.where.status = options.status;
|
options.where['posts.status'] = options.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
|
@ -400,7 +400,7 @@ Post = ghostBookshelf.Model.extend({
|
||||||
// these are the only options that can be passed to Bookshelf / Knex.
|
// these are the only options that can be passed to Bookshelf / Knex.
|
||||||
validOptions = {
|
validOptions = {
|
||||||
findOne: ['importing', 'withRelated'],
|
findOne: ['importing', 'withRelated'],
|
||||||
findPage: ['page', 'limit', 'columns', 'status', 'staticPages', 'featured'],
|
findPage: ['page', 'limit', 'columns', 'filter', 'status', 'staticPages', 'featured'],
|
||||||
add: ['importing']
|
add: ['importing']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
580
core/test/integration/api/advanced_browse_spec.js
Normal file
580
core/test/integration/api/advanced_browse_spec.js
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
/*globals describe, before, after, it */
|
||||||
|
/*jshint expr:true*/
|
||||||
|
var testUtils = require('../../utils'),
|
||||||
|
should = require('should'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
|
||||||
|
// Stuff we are testing
|
||||||
|
PostAPI = require('../../../server/api/posts'),
|
||||||
|
TagAPI = require('../../../server/api/tags'),
|
||||||
|
UserAPI = require('../../../server/api/users');
|
||||||
|
|
||||||
|
describe('Filter Param Spec', function () {
|
||||||
|
// Initialise the DB just once, the tests are fetch-only
|
||||||
|
before(testUtils.teardown);
|
||||||
|
before(testUtils.setup('filter'));
|
||||||
|
after(testUtils.teardown);
|
||||||
|
|
||||||
|
should.exist(PostAPI);
|
||||||
|
|
||||||
|
describe('Advanced Use Cases', function () {
|
||||||
|
describe('1. Posts - filter: "tags: [photo, video] + id: -4", limit: "3", include: "tags"', function () {
|
||||||
|
it('Will fetch 3 posts with tags which match `photo` or `video` and are not the post with id 4.', function (done) {
|
||||||
|
PostAPI.browse({filter: 'tags: [photo, video] + id: -4', limit: 3, include: 'tags'}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 3 items according to the limit property
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(3);
|
||||||
|
|
||||||
|
// None of the items returned should be the post with id 4, as that was excluded
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.not.containEql(4);
|
||||||
|
|
||||||
|
// Should not contain draft
|
||||||
|
ids.should.not.containEql(19);
|
||||||
|
|
||||||
|
// The ordering specifies that any post which matches both tags should be first
|
||||||
|
// Post 2 is the first in the list to have both tags
|
||||||
|
ids[0].should.eql(2);
|
||||||
|
|
||||||
|
// Each post should have a tag which matches either 'photo' or 'video'
|
||||||
|
_.each(result.posts, function (post) {
|
||||||
|
var slugs = _.pluck(post.tags, 'slug');
|
||||||
|
slugs.should.matchAny(/photo|video/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: match order, followed by publish date
|
||||||
|
// This isn't finished yet, as the 'special rule' ordering for matching 'in' requests hasn't been
|
||||||
|
// implemented properly.
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(3);
|
||||||
|
result.meta.pagination.pages.should.eql(3);
|
||||||
|
result.meta.pagination.total.should.eql(7);
|
||||||
|
result.meta.pagination.next.should.eql(2);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('2. Posts - filter: "tag:photo,featured:true,image:-null", include: "tags"', function () {
|
||||||
|
it('Will fetch posts which have either a tag of `photo`, are marked `featured` or have an image.', function (done) {
|
||||||
|
PostAPI.browse({filter: 'tag:photo,featured:true,image:-null', include: 'tags'}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(10);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([15, 14, 11, 9, 8, 7, 6, 5, 3, 2]);
|
||||||
|
|
||||||
|
// TODO: Should be in published order
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(10);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip('3. Tags - filter="post.count:>=1" order="posts.count DESC" limit="all"', function () {
|
||||||
|
it('Will fetch all tags, ordered by post count, where the post count is at least 1.', function (done) {
|
||||||
|
TagAPI.browse({filter: 'post.count:>=1', order: 'posts.count DESC', limit: 'all', include: 'posts.count'}).then(function (result) {
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('tags');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 3 matching items
|
||||||
|
result.tags.should.be.an.Array.with.lengthOf(3);
|
||||||
|
|
||||||
|
// TODO: add the ordering
|
||||||
|
// TODO: manage the count
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
// TODO complete meta data assertions
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('4. Posts - filter="author:[leslie,pat]+(featured:true,tag:audio)"', function () {
|
||||||
|
// Note that `pat` doesn't exist (it's `pat-smith`)
|
||||||
|
it('Will fetch posts by the author `leslie` or `pat` which are either featured or have tag `audio`.', function (done) {
|
||||||
|
PostAPI.browse({filter: 'author:[leslie,pat]+(featured:true,tag:audio)', include: 'author,tags'}).then(function (result) {
|
||||||
|
var ids, authors;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 2 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(4);
|
||||||
|
|
||||||
|
// Each post must either have the author 'leslie' or 'pat'
|
||||||
|
authors = _.map(result.posts, function (post) {
|
||||||
|
return post.author.slug;
|
||||||
|
});
|
||||||
|
authors.should.matchAny(/leslie|pat/);
|
||||||
|
|
||||||
|
// Each post must either be featured or have the tag 'audio'
|
||||||
|
_.each(result.posts, function (post) {
|
||||||
|
var tags;
|
||||||
|
// This construct ensures we get an assertion or a failure
|
||||||
|
if (post.featured === 'true') {
|
||||||
|
post.featured.should.be.true;
|
||||||
|
} else {
|
||||||
|
tags = _.pluck(post.tags, 'slug');
|
||||||
|
tags.should.containEql('audio');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([14, 12, 9, 8]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(4);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('5. Users - filter="posts.tags:photo" order="posts.count DESC" limit="3"', function () {
|
||||||
|
it('Will fetch the 3 most prolific users who write posts with the tag `photo` ordered by most posts.', function (done) {
|
||||||
|
UserAPI.browse({filter: 'posts.tags:photo', order: 'posts.count DESC', limit: 3}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('users');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 2 matching items
|
||||||
|
result.users.should.be.an.Array.with.lengthOf(2);
|
||||||
|
|
||||||
|
ids = _.pluck(result.users, 'id');
|
||||||
|
ids.should.eql([1, 2]);
|
||||||
|
|
||||||
|
// TODO: add the order
|
||||||
|
// TODO: manage the count
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(3);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(2);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip('6. Posts filter="published_at:>\'2015-07-20\'" limit="5"}}', function () {
|
||||||
|
it('Will fetch 5 posts after a given date.', function (done) {
|
||||||
|
PostAPI.browse({filter: 'published_at:>\'2015-07-20\'', limit: 5, include: 'tags'}).then(function (result) {
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// TODO: make dates work
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
// TODO complete meta data assertions
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip('Count capabilities', function () {
|
||||||
|
it('can fetch `posts.count` for tags (published only)', function (done) {
|
||||||
|
// This could be posts.count & posts.all.count?
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch `posts.all.count` for tags (all posts)', function (done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch `posts.count` for users (published only)', function (done) {
|
||||||
|
// This could be posts.count & posts.all.count?
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch `posts.all.count` for users (all posts)', function (done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch `tags.count` for posts', function (done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Old Use Cases', function () {
|
||||||
|
// Please note: these tests are mostly here to help prove certain things whilst building out new behaviour
|
||||||
|
describe('Old post "filters"', function () {
|
||||||
|
it('OLD STYLE TAG FILTER For checking against.. to be removed', function (done) {
|
||||||
|
PostAPI.browse({tag: 'photo', include: 'tag,author'}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 4 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(4);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([11, 9, 3, 2]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(4);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Will fetch posts with a given tag', function (done) {
|
||||||
|
PostAPI.browse({filter: 'tag:photo', include: 'tag,author'}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 4 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(4);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([11, 9, 3, 2]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(4);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: new query does not have meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OLD STYLE AUTHOR FILTER For checking against.. to be removed', function (done) {
|
||||||
|
PostAPI.browse({author: 'leslie', include: 'tag,author', limit: 5, page: 2}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(5);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([13, 12, 11, 10, 9]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(2);
|
||||||
|
result.meta.pagination.limit.should.eql(5);
|
||||||
|
result.meta.pagination.pages.should.eql(3);
|
||||||
|
result.meta.pagination.total.should.eql(15);
|
||||||
|
result.meta.pagination.next.should.eql(3);
|
||||||
|
result.meta.pagination.prev.should.eql(1);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Will fetch posts with a given author', function (done) {
|
||||||
|
PostAPI.browse({filter: 'author:leslie', include: 'tag,author', limit: 5, page: 2}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(5);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([13, 12, 11, 10, 9]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(2);
|
||||||
|
result.meta.pagination.limit.should.eql(5);
|
||||||
|
result.meta.pagination.pages.should.eql(3);
|
||||||
|
result.meta.pagination.total.should.eql(15);
|
||||||
|
result.meta.pagination.next.should.eql(3);
|
||||||
|
result.meta.pagination.prev.should.eql(1);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Handling "featured"', function () {
|
||||||
|
it('Will fetch all posts regardless of featured status by default', function (done) {
|
||||||
|
PostAPI.browse({}).then(function (result) {
|
||||||
|
var ids;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(15);
|
||||||
|
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([20, 18, 17, 16, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(2);
|
||||||
|
result.meta.pagination.total.should.eql(18);
|
||||||
|
result.meta.pagination.next.should.eql(2);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Will fetch only featured posts when requested', function (done) {
|
||||||
|
PostAPI.browse({filter: 'featured:true'}).then(function (result) {
|
||||||
|
var ids, featured;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(3);
|
||||||
|
|
||||||
|
// All posts should be marked as featured 'true'
|
||||||
|
featured = _.pluck(result.posts, 'featured');
|
||||||
|
featured.should.matchEach(true);
|
||||||
|
|
||||||
|
// Match exact items
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([14, 8, 5]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(3);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Will fetch only non-featured posts when requested', function (done) {
|
||||||
|
PostAPI.browse({filter: 'featured:false'}).then(function (result) {
|
||||||
|
var ids, featured;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(15);
|
||||||
|
|
||||||
|
// All posts should be marked as featured 'false'
|
||||||
|
featured = _.pluck(result.posts, 'featured');
|
||||||
|
featured.should.matchEach(false);
|
||||||
|
|
||||||
|
// Match exact items
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([20, 18, 17, 16, 13, 12, 11, 10, 9, 7, 6, 4, 3, 2, 1]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(15);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Handling "page" (staticPages)', function () {
|
||||||
|
it('Will return only posts by default', function (done) {
|
||||||
|
PostAPI.browse({limit: 'all'}).then(function (result) {
|
||||||
|
var ids, page;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(18);
|
||||||
|
|
||||||
|
// All posts should be marked as page 'false'
|
||||||
|
page = _.pluck(result.posts, 'page');
|
||||||
|
page.should.matchEach(false);
|
||||||
|
|
||||||
|
// Match exact items
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([20, 18, 17, 16, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql('all');
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(18);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: determine if this should be supported via filter, or whether it should only be available via a 'PageAPI'
|
||||||
|
it.skip('Will return only pages when requested', function (done) {
|
||||||
|
PostAPI.browse({filter: 'page:true'}).then(function (result) {
|
||||||
|
var ids, page;
|
||||||
|
// 1. Result should have the correct base structure
|
||||||
|
should.exist(result);
|
||||||
|
result.should.have.property('posts');
|
||||||
|
result.should.have.property('meta');
|
||||||
|
|
||||||
|
// 2. The data part of the response should be correct
|
||||||
|
// We should have 5 matching items
|
||||||
|
result.posts.should.be.an.Array.with.lengthOf(2);
|
||||||
|
|
||||||
|
// All posts should be marked as page 'true'
|
||||||
|
page = _.pluck(result.posts, 'page');
|
||||||
|
page.should.matchEach(true);
|
||||||
|
|
||||||
|
// Match exact items
|
||||||
|
ids = _.pluck(result.posts, 'id');
|
||||||
|
ids.should.eql([21, 15]);
|
||||||
|
|
||||||
|
// 3. The meta object should contain the right details
|
||||||
|
result.meta.should.have.property('pagination');
|
||||||
|
result.meta.pagination.should.be.an.Object.with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||||
|
result.meta.pagination.page.should.eql(1);
|
||||||
|
result.meta.pagination.limit.should.eql(15);
|
||||||
|
result.meta.pagination.pages.should.eql(1);
|
||||||
|
result.meta.pagination.total.should.eql(2);
|
||||||
|
should.equal(result.meta.pagination.next, null);
|
||||||
|
should.equal(result.meta.pagination.prev, null);
|
||||||
|
|
||||||
|
// NOTE: old query has meta filter
|
||||||
|
result.meta.should.not.have.property('filters');
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('Will NOT return both posts and pages from post API', function (done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,7 +18,7 @@ describe('API Utils', function () {
|
||||||
describe('Default Options', function () {
|
describe('Default Options', function () {
|
||||||
it('should provide a set of default options', function () {
|
it('should provide a set of default options', function () {
|
||||||
apiUtils.globalDefaultOptions.should.eql(['context', 'include']);
|
apiUtils.globalDefaultOptions.should.eql(['context', 'include']);
|
||||||
apiUtils.browseDefaultOptions.should.eql(['page', 'limit', 'fields']);
|
apiUtils.browseDefaultOptions.should.eql(['page', 'limit', 'fields', 'filter']);
|
||||||
apiUtils.dataDefaultOptions.should.eql(['data']);
|
apiUtils.dataDefaultOptions.should.eql(['data']);
|
||||||
apiUtils.idDefaultOptions.should.eql(['id']);
|
apiUtils.idDefaultOptions.should.eql(['id']);
|
||||||
});
|
});
|
||||||
|
|
|
@ -146,12 +146,12 @@ describe('pagination', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('query', function () {
|
describe('addLimitAndOffset', function () {
|
||||||
var query,
|
var addLimitAndOffset,
|
||||||
collection = {};
|
collection = {};
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
query = paginationUtils.query;
|
addLimitAndOffset = paginationUtils.addLimitAndOffset;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -159,7 +159,7 @@ describe('pagination', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add query options if limit is set', function () {
|
it('should add query options if limit is set', function () {
|
||||||
query(collection, {limit: 5, page: 1});
|
addLimitAndOffset(collection, {limit: 5, page: 1});
|
||||||
|
|
||||||
collection.query.calledTwice.should.be.true;
|
collection.query.calledTwice.should.be.true;
|
||||||
collection.query.firstCall.calledWith('limit', 5).should.be.true;
|
collection.query.firstCall.calledWith('limit', 5).should.be.true;
|
||||||
|
@ -167,7 +167,7 @@ describe('pagination', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add query options if limit is not set', function () {
|
it('should not add query options if limit is not set', function () {
|
||||||
query(collection, {page: 1});
|
addLimitAndOffset(collection, {page: 1});
|
||||||
|
|
||||||
collection.query.called.should.be.false;
|
collection.query.called.should.be.false;
|
||||||
});
|
});
|
||||||
|
@ -175,7 +175,7 @@ describe('pagination', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fetchPage', function () {
|
describe('fetchPage', function () {
|
||||||
var model, bookshelf, on, mockQuery, fetch, colQuery;
|
var model, bookshelf, knex, on, mockQuery, fetch, colQuery;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
paginationUtils = pagination.__get__('paginationUtils');
|
paginationUtils = pagination.__get__('paginationUtils');
|
||||||
|
@ -184,16 +184,16 @@ describe('pagination', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
// Stub paginationUtils
|
// Stub paginationUtils
|
||||||
paginationUtils.parseOptions = sandbox.stub();
|
paginationUtils.parseOptions = sandbox.stub();
|
||||||
paginationUtils.query = sandbox.stub();
|
paginationUtils.addLimitAndOffset = sandbox.stub();
|
||||||
paginationUtils.formatResponse = sandbox.stub().returns({});
|
paginationUtils.formatResponse = sandbox.stub().returns({});
|
||||||
|
|
||||||
// Mock out bookshelf model
|
// Mock out bookshelf model
|
||||||
mockQuery = {
|
mockQuery = {
|
||||||
clone: sandbox.stub(),
|
clone: sandbox.stub(),
|
||||||
count: sandbox.stub()
|
select: sandbox.stub()
|
||||||
};
|
};
|
||||||
mockQuery.clone.returns(mockQuery);
|
mockQuery.clone.returns(mockQuery);
|
||||||
mockQuery.count.returns([{aggregate: 1}]);
|
mockQuery.select.returns([{aggregate: 1}]);
|
||||||
|
|
||||||
fetch = sandbox.stub().returns(Promise.resolve({}));
|
fetch = sandbox.stub().returns(Promise.resolve({}));
|
||||||
colQuery = sandbox.stub();
|
colQuery = sandbox.stub();
|
||||||
|
@ -212,7 +212,9 @@ describe('pagination', function () {
|
||||||
model.prototype.resetQuery = sandbox.stub();
|
model.prototype.resetQuery = sandbox.stub();
|
||||||
model.prototype.query.returns(mockQuery);
|
model.prototype.query.returns(mockQuery);
|
||||||
|
|
||||||
bookshelf = {Model: model};
|
knex = {raw: sandbox.stub().returns(Promise.resolve())};
|
||||||
|
|
||||||
|
bookshelf = {Model: model, knex: knex};
|
||||||
|
|
||||||
pagination(bookshelf);
|
pagination(bookshelf);
|
||||||
});
|
});
|
||||||
|
@ -231,10 +233,10 @@ describe('pagination', function () {
|
||||||
model.prototype.constructor.collection,
|
model.prototype.constructor.collection,
|
||||||
model.prototype.query,
|
model.prototype.query,
|
||||||
mockQuery.clone,
|
mockQuery.clone,
|
||||||
mockQuery.count,
|
mockQuery.select,
|
||||||
model.prototype.query,
|
model.prototype.query,
|
||||||
mockQuery.clone,
|
mockQuery.clone,
|
||||||
paginationUtils.query,
|
paginationUtils.addLimitAndOffset,
|
||||||
on,
|
on,
|
||||||
on,
|
on,
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -244,7 +246,7 @@ describe('pagination', function () {
|
||||||
paginationUtils.parseOptions.calledOnce.should.be.true;
|
paginationUtils.parseOptions.calledOnce.should.be.true;
|
||||||
paginationUtils.parseOptions.calledWith(undefined).should.be.true;
|
paginationUtils.parseOptions.calledWith(undefined).should.be.true;
|
||||||
|
|
||||||
paginationUtils.query.calledOnce.should.be.true;
|
paginationUtils.addLimitAndOffset.calledOnce.should.be.true;
|
||||||
paginationUtils.formatResponse.calledOnce.should.be.true;
|
paginationUtils.formatResponse.calledOnce.should.be.true;
|
||||||
|
|
||||||
model.prototype.constructor.collection.calledOnce.should.be.true;
|
model.prototype.constructor.collection.calledOnce.should.be.true;
|
||||||
|
@ -258,8 +260,8 @@ describe('pagination', function () {
|
||||||
mockQuery.clone.firstCall.calledWith().should.be.true;
|
mockQuery.clone.firstCall.calledWith().should.be.true;
|
||||||
mockQuery.clone.secondCall.calledWith().should.be.true;
|
mockQuery.clone.secondCall.calledWith().should.be.true;
|
||||||
|
|
||||||
mockQuery.count.calledOnce.should.be.true;
|
mockQuery.select.calledOnce.should.be.true;
|
||||||
mockQuery.count.calledWith().should.be.true;
|
mockQuery.select.calledWith().should.be.true;
|
||||||
|
|
||||||
on.calledTwice.should.be.true;
|
on.calledTwice.should.be.true;
|
||||||
on.firstCall.calledWith('fetching').should.be.true;
|
on.firstCall.calledWith('fetching').should.be.true;
|
||||||
|
@ -282,10 +284,10 @@ describe('pagination', function () {
|
||||||
model.prototype.constructor.collection,
|
model.prototype.constructor.collection,
|
||||||
model.prototype.query,
|
model.prototype.query,
|
||||||
mockQuery.clone,
|
mockQuery.clone,
|
||||||
mockQuery.count,
|
mockQuery.select,
|
||||||
model.prototype.query,
|
model.prototype.query,
|
||||||
mockQuery.clone,
|
mockQuery.clone,
|
||||||
paginationUtils.query,
|
paginationUtils.addLimitAndOffset,
|
||||||
colQuery,
|
colQuery,
|
||||||
on,
|
on,
|
||||||
on,
|
on,
|
||||||
|
@ -296,7 +298,7 @@ describe('pagination', function () {
|
||||||
paginationUtils.parseOptions.calledOnce.should.be.true;
|
paginationUtils.parseOptions.calledOnce.should.be.true;
|
||||||
paginationUtils.parseOptions.calledWith(orderOptions).should.be.true;
|
paginationUtils.parseOptions.calledWith(orderOptions).should.be.true;
|
||||||
|
|
||||||
paginationUtils.query.calledOnce.should.be.true;
|
paginationUtils.addLimitAndOffset.calledOnce.should.be.true;
|
||||||
paginationUtils.formatResponse.calledOnce.should.be.true;
|
paginationUtils.formatResponse.calledOnce.should.be.true;
|
||||||
|
|
||||||
model.prototype.constructor.collection.calledOnce.should.be.true;
|
model.prototype.constructor.collection.calledOnce.should.be.true;
|
||||||
|
@ -310,8 +312,8 @@ describe('pagination', function () {
|
||||||
mockQuery.clone.firstCall.calledWith().should.be.true;
|
mockQuery.clone.firstCall.calledWith().should.be.true;
|
||||||
mockQuery.clone.secondCall.calledWith().should.be.true;
|
mockQuery.clone.secondCall.calledWith().should.be.true;
|
||||||
|
|
||||||
mockQuery.count.calledOnce.should.be.true;
|
mockQuery.select.calledOnce.should.be.true;
|
||||||
mockQuery.count.calledWith().should.be.true;
|
mockQuery.select.calledWith().should.be.true;
|
||||||
|
|
||||||
colQuery.calledOnce.should.be.true;
|
colQuery.calledOnce.should.be.true;
|
||||||
colQuery.calledWith('orderBy', 'undefined.id', 'DESC').should.be.true;
|
colQuery.calledWith('orderBy', 'undefined.id', 'DESC').should.be.true;
|
||||||
|
|
312
core/test/utils/fixtures/filter-param/index.js
Normal file
312
core/test/utils/fixtures/filter-param/index.js
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
/**
|
||||||
|
* These fixtures are just for testing the filter spec
|
||||||
|
*/
|
||||||
|
var _ = require('lodash'),
|
||||||
|
config = require('../../../../server/config'),
|
||||||
|
data = {};
|
||||||
|
|
||||||
|
data.tags = [
|
||||||
|
{
|
||||||
|
name: 'Getting Started',
|
||||||
|
slug: 'getting-started',
|
||||||
|
created_by: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'photo',
|
||||||
|
slug: 'photo',
|
||||||
|
created_by: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Video',
|
||||||
|
slug: 'video',
|
||||||
|
created_by: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Audio',
|
||||||
|
slug: 'audio',
|
||||||
|
created_by: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No Posts',
|
||||||
|
slug: 'no-posts',
|
||||||
|
created_by: 2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Password = Sl1m3rson
|
||||||
|
data.users = [
|
||||||
|
{
|
||||||
|
name: 'Leslie Jones',
|
||||||
|
slug: 'leslie',
|
||||||
|
email: 'ljones@nothere.com',
|
||||||
|
password: '$2a$10$.pZeeBE0gHXd0PTnbT/ph.GEKgd0Wd3q2pWna3ynTGBkPKnGIKZL6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Pat Smith',
|
||||||
|
slug: 'pat-smith',
|
||||||
|
email: 'pat-smith@nothere.com',
|
||||||
|
password: '$2a$10$.pZeeBE0gHXd0PTnbT/ph.GEKgd0Wd3q2pWna3ynTGBkPKnGIKZL6'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
data.posts = [
|
||||||
|
{
|
||||||
|
title: 'First Post',
|
||||||
|
slug: 'first-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Second Post',
|
||||||
|
slug: 'second-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 2,
|
||||||
|
tags: [2, 3, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Third Post',
|
||||||
|
slug: 'third-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fourth Post',
|
||||||
|
slug: 'fourth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fifth Post',
|
||||||
|
slug: 'fifth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: true,
|
||||||
|
author_id: 2,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sixth Post',
|
||||||
|
slug: 'sixth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 2,
|
||||||
|
image: 'some/image/path.jpg',
|
||||||
|
tags: [1, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Seventh Post',
|
||||||
|
slug: 'seventh-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
image: 'some/image/path.jpg',
|
||||||
|
tags: [1, 3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Eighth Post',
|
||||||
|
slug: 'eighth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: true,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [1, 3, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Ninth Post',
|
||||||
|
slug: 'ninth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [2, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tenth Post',
|
||||||
|
slug: 'tenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Eleventh Post',
|
||||||
|
slug: 'eleventh-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
image: 'some/image/path.jpg',
|
||||||
|
tags: [2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Twelfth Post',
|
||||||
|
slug: 'twelfth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Thirteenth Post',
|
||||||
|
slug: 'thirteenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fourteenth Post',
|
||||||
|
slug: 'fourteenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: true,
|
||||||
|
author_id: 1,
|
||||||
|
tags: [4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fifteenth Post',
|
||||||
|
slug: 'fifteenth-post',
|
||||||
|
markdown: 'Hello World! I am a featured page',
|
||||||
|
featured: true,
|
||||||
|
page: 1,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sixteenth Post',
|
||||||
|
slug: 'sixteenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Seventeenth Post',
|
||||||
|
slug: 'seventeenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Eighteenth Post',
|
||||||
|
slug: 'eighteenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Nineteenth Post',
|
||||||
|
slug: 'nineteenth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
status: 'draft',
|
||||||
|
author_id: 1,
|
||||||
|
tags: [1, 2, 3, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Twentieth Post',
|
||||||
|
slug: 'twentieth-post',
|
||||||
|
markdown: 'Hello World!',
|
||||||
|
featured: false,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'About Page',
|
||||||
|
slug: 'about',
|
||||||
|
markdown: 'About Me!',
|
||||||
|
featured: false,
|
||||||
|
page: 1,
|
||||||
|
author_id: 1,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function fixDataIndexes(origData, storedData) {
|
||||||
|
var indexedData = {};
|
||||||
|
_.each(origData, function (orig, index) {
|
||||||
|
indexedData[index + 1] = _.find(storedData, function (stored) {
|
||||||
|
return stored.slug === orig.slug;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return indexedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFetchFix(knex, resource) {
|
||||||
|
return knex(resource).insert(data[resource]).then(function () {
|
||||||
|
return knex(resource).select();
|
||||||
|
}).then(function (stored) {
|
||||||
|
return fixDataIndexes(data[resource], stored);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUsers(knex, DataGenerator) {
|
||||||
|
// First, loop through and prep the data
|
||||||
|
data.users = _.map(data.users, function (user) {
|
||||||
|
return DataGenerator.forKnex.createUser(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, insert it into the database & return the correctly indexed data
|
||||||
|
return writeFetchFix(knex, 'users');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTags(knex, DataGenerator, created) {
|
||||||
|
data.tags = _.map(data.tags, function (tag) {
|
||||||
|
tag = DataGenerator.forKnex.createBasic(tag);
|
||||||
|
tag.created_by = created.users[tag.created_by].id;
|
||||||
|
return tag;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, insert it into the database & return the correctly indexed data
|
||||||
|
return writeFetchFix(knex, 'tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPosts(knex, DataGenerator, created) {
|
||||||
|
var postsTags = [];
|
||||||
|
data.posts = _.map(data.posts, function (post, index) {
|
||||||
|
post = DataGenerator.forKnex.createPost(post);
|
||||||
|
post.created_by = created.users[post.author_id].id;
|
||||||
|
post.author_id = created.users[post.author_id].id;
|
||||||
|
_.each(post.tags, function (tagId) {
|
||||||
|
postsTags.push({post_id: index + 1, tag_id: created.tags[tagId].id});
|
||||||
|
});
|
||||||
|
delete post.tags;
|
||||||
|
return post;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, insert it into the database & return the correctly indexed data
|
||||||
|
return writeFetchFix(knex, 'posts').then(function (createdPosts) {
|
||||||
|
// Handle post tags
|
||||||
|
postsTags = _.map(postsTags, function (postTag) {
|
||||||
|
postTag.post_id = createdPosts[postTag.post_id].id;
|
||||||
|
return postTag;
|
||||||
|
});
|
||||||
|
|
||||||
|
return knex('posts_tags').insert(postsTags).then(function () {
|
||||||
|
return createdPosts;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (DataGenerator) {
|
||||||
|
var knex = config.database.knex,
|
||||||
|
created = {};
|
||||||
|
// Create users first
|
||||||
|
return createUsers(knex, DataGenerator).then(function (createdUsers) {
|
||||||
|
created.users = createdUsers;
|
||||||
|
// Next create tags
|
||||||
|
return createTags(knex, DataGenerator, created);
|
||||||
|
}).then(function (createdTags) {
|
||||||
|
created.tags = createdTags;
|
||||||
|
// Finally, setup posts with the right authors and tags
|
||||||
|
return createPosts(knex, DataGenerator, created);
|
||||||
|
}).then(function (createdPosts) {
|
||||||
|
created.posts = createdPosts;
|
||||||
|
return created;
|
||||||
|
});
|
||||||
|
};
|
|
@ -10,6 +10,7 @@ var Promise = require('bluebird'),
|
||||||
permissions = require('../../server/permissions'),
|
permissions = require('../../server/permissions'),
|
||||||
permsFixtures = require('../../server/data/fixtures/permissions/permissions.json'),
|
permsFixtures = require('../../server/data/fixtures/permissions/permissions.json'),
|
||||||
DataGenerator = require('./fixtures/data-generator'),
|
DataGenerator = require('./fixtures/data-generator'),
|
||||||
|
filterData = require('./fixtures/filter-param'),
|
||||||
API = require('./api'),
|
API = require('./api'),
|
||||||
fork = require('./fork'),
|
fork = require('./fork'),
|
||||||
config = require('../../server/config'),
|
config = require('../../server/config'),
|
||||||
|
@ -419,7 +420,8 @@ toDoList = {
|
||||||
perms: function permissionsFor(obj) {
|
perms: function permissionsFor(obj) {
|
||||||
return function permissionsForObj() { return fixtures.permissionsFor(obj); };
|
return function permissionsForObj() { return fixtures.permissionsFor(obj); };
|
||||||
},
|
},
|
||||||
clients: function insertClients() { return fixtures.insertClients(); }
|
clients: function insertClients() { return fixtures.insertClients(); },
|
||||||
|
filter: function createFilterParamFixtures() { return filterData(DataGenerator); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"express-hbs": "0.8.4",
|
"express-hbs": "0.8.4",
|
||||||
"extract-zip": "1.1.1",
|
"extract-zip": "1.1.1",
|
||||||
"fs-extra": "0.24.0",
|
"fs-extra": "0.24.0",
|
||||||
|
"ghost-gql": "0.0.2",
|
||||||
"glob": "5.0.15",
|
"glob": "5.0.15",
|
||||||
"html-to-text": "1.3.2",
|
"html-to-text": "1.3.2",
|
||||||
"intl": "1.0.0",
|
"intl": "1.0.0",
|
||||||
|
|
Loading…
Add table
Reference in a new issue