text: var should = require('should'),
      testUtils = require('../../utils'),
      _ = require('lodash'),
  
  // Stuff we are testing
      PostAPI = require('../../../server/api/posts'),
      TagAPI = require('../../../server/api/tags'),
      UserAPI = require('../../../server/api/users');
  
  describe('Advanced Browse', function () {
      // Initialise the DB just once, the tests are fetch-only
      before(testUtils.teardown);
      before(testUtils.setup('filter'));
      after(testUtils.teardown);
  
      should.exist(PostAPI);
      should.exist(TagAPI);
      should.exist(UserAPI);
  
      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: -' + testUtils.filterData.data.posts[3].id,
                      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 = _.map(result.posts, 'id');
                      ids.should.not.containEql(testUtils.filterData.data.posts[3].id);
  
                      // Should not contain draft
                      ids.should.not.containEql(testUtils.filterData.data.posts[18].id);
  
                      // 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(testUtils.filterData.data.posts[1].id);
  
                      // Each post should have a tag which matches either 'photo' or 'video'
                      _.each(result.posts, function (post) {
                          var slugs = _.map(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,feature_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(9);
  
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[7].id,
                          testUtils.filterData.data.posts[6].id,
                          testUtils.filterData.data.posts[5].id,
                          testUtils.filterData.data.posts[4].id,
                          testUtils.filterData.data.posts[2].id,
                          testUtils.filterData.data.posts[1].id
                      ]);
  
                      _.each(result.posts, function (post) {
                          post.page.should.be.false();
                          post.status.should.eql('published');
                      });
  
                      // 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(9);
                      should.equal(result.meta.pagination.next, null);
                      should.equal(result.meta.pagination.prev, null);
  
                      done();
                  }).catch(done);
              });
          });
  
          describe.skip('3. Tags - filter="count.posts:>=1" order="count.posts DESC" limit="all"', function () {
              // @TODO add support for counts/aggregates in order & filter params
              it('Will fetch all tags, ordered by post count, where the post count is at least 1.', function (done) {
                  TagAPI.browse({
                      filter: 'count.posts:>=1',
                      order: 'count.posts DESC',
                      limit: 'all',
                      include: 'count.posts'
                  }).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]+(tag:hash-audio,image:-null)"', function () {
              // Note that `pat` doesn't exist (it's `pat-smith`)
              it('[DEPRECATED] will fetch posts by the author `leslie` or `pat` which are either have tag `hash-audio` or an image.', function (done) {
                  PostAPI.browse({
                      filter: 'author:[leslie,pat]+(tag:hash-audio,feature_image:-null)',
                      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(6);
  
                      // 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 'hash-audio'
                      _.each(result.posts, function (post) {
                          var tags = _.map(post.tags, 'slug');
                          // This construct ensures we get an assertion or a failure
                          if (!_.isEmpty(post.feature_image)) {
                              post.feature_image.should.not.be.empty();
                          } else {
                              tags = _.map(post.tags, 'slug');
                              tags.should.containEql('hash-audio');
                          }
                      });
  
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[7].id,
                          testUtils.filterData.data.posts[6].id
                      ]);
  
                      // 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(6);
                      should.equal(result.meta.pagination.next, null);
                      should.equal(result.meta.pagination.prev, null);
  
                      done();
                  }).catch(done);
              });
  
              it('will fetch posts by the authors `leslie` or `pat` which are either have tag `hash-audio` or an image.', function (done) {
                  PostAPI.browse({
                      filter: 'authors:[leslie,pat]+(tag:hash-audio,feature_image:-null)',
                      include: 'authors,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(6);
  
                      // Each post must either have the author 'leslie' or 'pat'
                      authors = _.map(result.posts, function (post) {
                          return post.authors[0].slug;
                      });
                      authors.should.matchAny(/leslie|pat/);
  
                      // Each post must either be featured or have the tag 'hash-audio'
                      _.each(result.posts, function (post) {
                          var tags = _.map(post.tags, 'slug');
                          // This construct ensures we get an assertion or a failure
                          if (!_.isEmpty(post.feature_image)) {
                              post.feature_image.should.not.be.empty();
                          } else {
                              tags = _.map(post.tags, 'slug');
                              tags.should.containEql('hash-audio');
                          }
                      });
  
                      ids = _.map(result.posts, 'id');
  
                      // ordered by authors.id
                      ids.should.eql([
                          testUtils.filterData.data.posts[6].id,
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[7].id
                      ]);
  
                      // 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(6);
                      should.equal(result.meta.pagination.next, null);
                      should.equal(result.meta.pagination.prev, null);
  
                      done();
                  }).catch(done);
              });
          });
  
          describe.skip('5. Users - filter="posts.tags:photo" order="count.posts DESC" limit="3"', function () {
              // @TODO: add support for joining through posts and tags for users
              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:special',
                      order: 'count.posts 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 = _.map(result.users, 'id');
  
                      ids.should.eql([
                          testUtils.filterData.data.posts[0].id,
                          testUtils.filterData.data.posts[1].id
                      ]);
  
                      // 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('7. Users filter: "website:-null", order: "website"', function () {
              it('Will fetch users that have a website and order them by website', function (done) {
                  UserAPI.browse({filter: 'website:-null', order: 'website ASC'}).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 = _.map(result.users, 'id');
  
                      ids.should.eql([
                          testUtils.filterData.data.users[1].id,
                          testUtils.filterData.data.users[0].id
                      ]);
  
                      should.exist(result.users[0].website);
                      should.exist(result.users[1].website);
  
                      // 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);
  
                      done();
                  }).catch(done);
              });
          });
  
          describe('8. Tags filter: "image:-null+description:-null"', function () {
              it('Will fetch tags which have an image and a description', function (done) {
                  TagAPI.browse({
                      filter: 'feature_image:-null+description:-null',
                      order: 'name ASC'
                  }).then(function (result) {
                      var ids;
                      // 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);
  
                      ids = _.map(result.tags, 'id');
                      ids.should.containEql(testUtils.filterData.data.tags[3].id);
                      ids.should.containEql(testUtils.filterData.data.tags[2].id);
                      ids.should.containEql(testUtils.filterData.data.tags[1].id);
  
                      // @TODO standardise how alphabetical ordering is done across DBs (see #6104)
                      // ids.should.eql([4, 2, 3]);
  
                      should.exist(result.tags[0].feature_image);
                      should.exist(result.tags[1].feature_image);
                      should.exist(result.tags[2].feature_image);
  
                      should.exist(result.tags[0].description);
                      should.exist(result.tags[1].description);
                      should.exist(result.tags[2].description);
  
                      // 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);
  
                      done();
                  }).catch(done);
              });
          });
      });
  
      describe('Primary Tags / Primary Authors', function () {
          it('Will fetch posts which have a primary tag of photo', function (done) {
              PostAPI.browse({
                  filter: 'primary_tag:photo',
                  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(4);
  
                  ids = _.map(result.posts, 'id');
                  ids.should.eql([
                      testUtils.filterData.data.posts[10].id,
                      testUtils.filterData.data.posts[8].id,
                      testUtils.filterData.data.posts[2].id,
                      testUtils.filterData.data.posts[1].id
                  ]);
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  // 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);
          });
  
          it('Will fetch posts which have a primary author', function (done) {
              PostAPI.browse({
                  filter: 'primary_author:leslie',
                  include: 'authors'
              }).then(function (result) {
                  var returnedIds, insertedIds, clonedInsertedPosts;
  
                  // 1. Result should have the correct base structure
                  should.exist(result);
                  result.should.have.property('posts');
                  result.should.have.property('meta');
  
                  // all posts
                  testUtils.filterData.data.posts.length.should.eql(21);
  
                  // 15 have the primary author leslie
                  result.posts.should.be.an.Array().with.lengthOf(15);
  
                  returnedIds = _.map(result.posts, 'id');
  
                  insertedIds = _.filter(testUtils.filterData.data.posts, {status: 'published'});
                  insertedIds = _.filter(insertedIds, {page: false});
                  insertedIds = _.filter(insertedIds, {author_id: testUtils.filterData.data.users[0].id});
  
                  insertedIds = _.map(insertedIds, 'id');
                  insertedIds.length.should.eql(15);
  
                  insertedIds.reverse();
  
                  returnedIds.should.eql(insertedIds);
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  // 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);
  
                  done();
              }).catch(done);
          });
  
          it('Will fetch empty list if no post has matching primary-tag', function (done) {
              PostAPI.browse({
                  filter: 'primary_tag:no-posts',
                  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
                  // We should have 5 matching items
                  result.posts.should.be.an.Array().with.lengthOf(0);
  
                  // 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(0);
                  should.equal(result.meta.pagination.next, null);
                  should.equal(result.meta.pagination.prev, null);
  
                  done();
              }).catch(done);
          });
  
          it('Will fetch empty list if primary_tag is internal', function (done) {
              PostAPI.browse({
                  filter: 'primary_tag:no-posts',
                  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
                  // We should have 5 matching items
                  result.posts.should.be.an.Array().with.lengthOf(0);
  
                  // 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(0);
                  should.equal(result.meta.pagination.next, null);
                  should.equal(result.meta.pagination.prev, null);
  
                  done();
              }).catch(done);
          });
      });
  
      describe('Count capabilities', function () {
          it('can fetch `count.posts` for tags (public data only)', function (done) {
              TagAPI.browse({include: 'count.posts'}).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 5 matching items
                  result.tags.should.be.an.Array().with.lengthOf(6);
  
                  // Each tag should have the correct count
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Getting Started';
                  }).count.posts.should.eql(4);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'photo';
                  }).count.posts.should.eql(4);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Video';
                  }).count.posts.should.eql(5);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === '#Audio';
                  }).count.posts.should.eql(6);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'No Posts';
                  }).count.posts.should.eql(0);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Special';
                  }).count.posts.should.eql(3);
  
                  // 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(6);
                  should.equal(result.meta.pagination.next, null);
                  should.equal(result.meta.pagination.prev, null);
  
                  done();
              }).catch(done);
          });
  
          it('can fetch and order by `count.posts` for tags (public data only)', function (done) {
              TagAPI.browse({include: 'count.posts', order: 'count.posts DESC'}).then(function (result) {
                  var ids;
  
                  // 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 5 matching items
                  result.tags.should.be.an.Array().with.lengthOf(6);
  
                  // Each tag should have the correct count
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Getting Started';
                  }).count.posts.should.eql(4);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'photo';
                  }).count.posts.should.eql(4);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Video';
                  }).count.posts.should.eql(5);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === '#Audio';
                  }).count.posts.should.eql(6);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'No Posts';
                  }).count.posts.should.eql(0);
  
                  _.find(result.tags, function (tag) {
                      return tag.name === 'Special';
                  }).count.posts.should.eql(3);
  
                  ids = _.map(result.tags, 'id');
                  ids.should.eql([
                      testUtils.filterData.data.tags[3].id,
                      testUtils.filterData.data.tags[2].id,
                      testUtils.filterData.data.tags[0].id,
                      testUtils.filterData.data.tags[1].id,
                      testUtils.filterData.data.tags[5].id,
                      testUtils.filterData.data.tags[4].id
                  ]);
  
                  // 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(6);
                  should.equal(result.meta.pagination.next, null);
                  should.equal(result.meta.pagination.prev, null);
  
                  done();
              }).catch(done);
          });
  
          it.skip('can fetch `count.posts` for tags (all data)', function (done) {
              // This is tested elsewhere for now using user context
              // No way to override it for public requests
              done();
          });
  
          it('can fetch `count.posts` for users (published only)', function (done) {
              UserAPI.browse({include: 'count.posts'}).then(function (result) {
                  // 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 5 matching items
                  result.users.should.be.an.Array().with.lengthOf(3);
  
                  // Each user should have the correct count
                  _.find(result.users, function (user) {
                      return user.slug === 'leslie';
                  }).count.posts.should.eql(15);
  
                  _.find(result.users, function (user) {
                      return user.slug === 'pat-smith';
                  }).count.posts.should.eql(3);
  
                  _.find(result.users, function (user) {
                      return user.slug === 'camhowe';
                  }).count.posts.should.eql(0);
  
                  // 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);
  
                  done();
              }).catch(done);
          });
  
          it('can fetch and order by `count.posts` for users (published only)', function (done) {
              UserAPI.browse({include: 'count.posts', order: 'count.posts ASC'}).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 5 matching items
                  result.users.should.be.an.Array().with.lengthOf(3);
  
                  // Each user should have the correct count
                  _.find(result.users, function (user) {
                      return user.slug === 'leslie';
                  }).count.posts.should.eql(15);
  
                  _.find(result.users, function (user) {
                      return user.slug === 'pat-smith';
                  }).count.posts.should.eql(3);
  
                  _.find(result.users, function (user) {
                      return user.slug === 'camhowe';
                  }).count.posts.should.eql(0);
  
                  ids = _.map(result.users, 'id');
  
                  ids.should.eql([
                      testUtils.filterData.data.users[2].id,
                      testUtils.filterData.data.users[1].id,
                      testUtils.filterData.data.users[0].id
                  ]);
  
                  // 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);
  
                  done();
              }).catch(done);
          });
  
          it.skip('can fetch `posts.all.count` for users (all posts)', function (done) {
              done();
          });
  
          it.skip('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('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 = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[2].id,
                          testUtils.filterData.data.posts[1].id
                      ]);
  
                      // 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('[DEPRECATED] 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 = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[12].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[9].id,
                          testUtils.filterData.data.posts[8].id
                      ]);
  
                      // 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 = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[19].id,
                          testUtils.filterData.data.posts[17].id,
                          testUtils.filterData.data.posts[16].id,
                          testUtils.filterData.data.posts[15].id,
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[12].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[9].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[7].id,
                          testUtils.filterData.data.posts[6].id,
                          testUtils.filterData.data.posts[5].id,
                          testUtils.filterData.data.posts[4].id,
                          testUtils.filterData.data.posts[3].id
                      ]);
  
                      // 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 = _.map(result.posts, 'featured');
                      featured.should.matchEach(true);
  
                      // Match exact items
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[7].id,
                          testUtils.filterData.data.posts[4].id
                      ]);
  
                      // 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 = _.map(result.posts, 'featured');
                      featured.should.matchEach(false);
  
                      // Match exact items
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[19].id,
                          testUtils.filterData.data.posts[17].id,
                          testUtils.filterData.data.posts[16].id,
                          testUtils.filterData.data.posts[15].id,
                          testUtils.filterData.data.posts[12].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[9].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[6].id,
                          testUtils.filterData.data.posts[5].id,
                          testUtils.filterData.data.posts[3].id,
                          testUtils.filterData.data.posts[2].id,
                          testUtils.filterData.data.posts[1].id,
                          testUtils.filterData.data.posts[0].id
                      ]);
  
                      // 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 published 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 = _.map(result.posts, 'page');
                      page.should.matchEach(false);
  
                      // Match exact items
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[19].id,
                          testUtils.filterData.data.posts[17].id,
                          testUtils.filterData.data.posts[16].id,
                          testUtils.filterData.data.posts[15].id,
                          testUtils.filterData.data.posts[13].id,
                          testUtils.filterData.data.posts[12].id,
                          testUtils.filterData.data.posts[11].id,
                          testUtils.filterData.data.posts[10].id,
                          testUtils.filterData.data.posts[9].id,
                          testUtils.filterData.data.posts[8].id,
                          testUtils.filterData.data.posts[7].id,
                          testUtils.filterData.data.posts[6].id,
                          testUtils.filterData.data.posts[5].id,
                          testUtils.filterData.data.posts[4].id,
                          testUtils.filterData.data.posts[3].id,
                          testUtils.filterData.data.posts[2].id,
                          testUtils.filterData.data.posts[1].id,
                          testUtils.filterData.data.posts[0].id
                      ]);
  
                      // 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('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 = _.map(result.posts, 'page');
                      page.should.matchEach(true);
  
                      // Match exact items
                      ids = _.map(result.posts, 'id');
                      ids.should.eql([
                          testUtils.filterData.data.posts[20].id,
                          testUtils.filterData.data.posts[14].id
                      ]);
  
                      // 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();
              });
          });
  
          describe('Empty results', function () {
              it('Will return empty result if tag has no posts', function (done) {
                  PostAPI.browse({filter: 'tag:no-posts', include: 'tag,author'}).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
                      // We should have 4 matching items
                      result.posts.should.be.an.Array().with.lengthOf(0);
  
                      // 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(0);
                      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);
              });
          });
      });
  
      describe('Bad behaviour', function () {
          it('Try to get draft posts (filter with or)', function (done) {
              PostAPI.browse({filter: 'status:published,status:draft', limit: 'all'}).then(function (result) {
                  // 1. Result should have the correct base structure
                  should.exist(result);
                  result.should.have.property('posts');
                  result.should.have.property('meta');
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  done();
              }).catch(done);
          });
  
          it('Try to get draft posts (filter with in)', function (done) {
              PostAPI.browse({filter: 'status:[published,draft]', limit: 'all'}).then(function (result) {
                  // 1. Result should have the correct base structure
                  should.exist(result);
                  result.should.have.property('posts');
                  result.should.have.property('meta');
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  done();
              }).catch(done);
          });
  
          it('Try to get draft posts (filter with group)', function (done) {
              PostAPI.browse({filter: 'page:false,(status:draft)', limit: 'all'}).then(function (result) {
                  // 1. Result should have the correct base structure
                  should.exist(result);
                  result.should.have.property('posts');
                  result.should.have.property('meta');
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  done();
              }).catch(done);
          });
  
          it('Try to get draft posts (filter with group and in)', function (done) {
              PostAPI.browse({filter: 'page:false,(status:[draft,published])', limit: 'all'}).then(function (result) {
                  // 1. Result should have the correct base structure
                  should.exist(result);
                  result.should.have.property('posts');
                  result.should.have.property('meta');
  
                  _.each(result.posts, function (post) {
                      post.page.should.be.false();
                      post.status.should.eql('published');
                  });
  
                  done();
              }).catch(done);
          });
      });
  });
  