0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Enforced non-page posts only to be returned by /posts endpoint from Content API (#10002)

refs #9866

- Added logic ensuring page filter is always set to false in posts endpoint for Content API
- Added functional tests to pages and posts
- Added absolute_url logic in pages controller
This commit is contained in:
Nazar Gargol 2018-10-13 00:48:49 +02:00 committed by GitHub
parent 981ad28283
commit 76f4a4bb03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 717 additions and 29 deletions

View file

@ -15,7 +15,8 @@ const http = (apiImpl) => {
user: req.user,
context: {
api_key_id: (req.api_key && req.api_key.id) ? req.api_key.id : null,
user: ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) ? req.user.id : null
user: ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) ? req.user.id : null,
client_id: (req.client && req.client.id) ? req.client.id : null // TODO: @allouis please remove this once Content API auth is in place
}
});

View file

@ -1,7 +1,32 @@
const _ = require('lodash');
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:posts');
module.exports = {
all(apiConfig, frame) {
debug('all');
if (!_.get(frame, 'options.context.user') && _.get(frame, 'options.context.client_id')) {
// CASE: the content api endpoints for posts should only return non page type resources
if (frame.options.filter) {
if (frame.options.filter.match(/page:\w+\+?/)) {
frame.options.filter = frame.options.filter.replace(/page:\w+\+?/, '');
}
if (frame.options.filter) {
frame.options.filter = frame.options.filter + '+page:false';
} else {
frame.options.filter = 'page:false';
}
} else {
frame.options.filter = 'page:false';
}
}
debug(frame.options);
},
add(apiConfig, frame) {
debug('add');
/**
* Convert author property to author_id to match the name in the database.
*

View file

@ -1,4 +1,76 @@
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:pages');
const urlService = require('../../../../../services/url');
// @TODO: refactor if we add users+tags controllers
const urlsForUser = (user) => {
user.url = urlService.getUrlByResourceId(user.id, {absolute: true});
if (user.profile_image) {
user.profile_image = urlService.utils.urlFor('image', {image: user.profile_image}, true);
}
if (user.cover_image) {
user.cover_image = urlService.utils.urlFor('image', {image: user.cover_image}, true);
}
return user;
};
const urlsForTag = (tag) => {
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
if (tag.feature_image) {
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
}
return tag;
};
// @TODO: Update the url decoration in https://github.com/TryGhost/Ghost/pull/9969.
const absoluteUrls = (attrs, options) => {
attrs.url = urlService.getUrlByResourceId(attrs.id, {absolute: true});
if (attrs.feature_image) {
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
}
if (attrs.og_image) {
attrs.og_image = urlService.utils.urlFor('image', {image: attrs.og_image}, true);
}
if (attrs.twitter_image) {
attrs.twitter_image = urlService.utils.urlFor('image', {image: attrs.twitter_image}, true);
}
if (attrs.html) {
attrs.html = urlService.utils.makeAbsoluteUrls(attrs.html, urlService.utils.urlFor('home', true), attrs.url).html();
}
if (options.columns && !options.columns.includes('url')) {
delete attrs.url;
}
if (options && options.withRelated) {
options.withRelated.forEach((relation) => {
// @NOTE: this block also decorates primary_tag/primary_author objects as they
// are being passed by reference in tags/authors. Might be refactored into more explicit call
// in the future, but is good enough for current use-case
if (relation === 'tags' && attrs.tags) {
attrs.tags = attrs.tags.map(tag => urlsForTag(tag));
}
if (relation === 'author' && attrs.author) {
attrs.author = urlsForUser(attrs.author);
}
if (relation === 'authors' && attrs.authors) {
attrs.authors = attrs.authors.map(author => urlsForUser(author));
}
});
}
return attrs;
};
module.exports = {
all(models, apiConfig, frame) {
@ -6,7 +78,7 @@ module.exports = {
if (models.meta) {
frame.response = {
pages: models.data.map(model => model.toJSON(frame.options)),
pages: models.data.map(model => absoluteUrls(model.toJSON(frame.options), frame.options)),
meta: models.meta
};
@ -14,7 +86,7 @@ module.exports = {
}
frame.response = {
pages: [models.toJSON(frame.options)]
pages: [absoluteUrls(models.toJSON(frame.options), frame.options)]
};
debug(frame.response);

View file

@ -17,9 +17,9 @@ module.exports = function apiRoutes() {
router.get('/configuration', api.http(api.configuration.read));
// ## Posts
router.get('/posts', mw.authenticatePublic, api.http(api.posts.browse));
router.get('/posts/:id', mw.authenticatePublic, api.http(api.posts.read));
router.get('/posts/slug/:slug', mw.authenticatePublic, api.http(api.posts.read));
router.get('/posts', mw.authenticatePublic, apiv2.http(apiv2.posts.browse));
router.get('/posts/:id', mw.authenticatePublic, apiv2.http(apiv2.posts.read));
router.get('/posts/slug/:slug', mw.authenticatePublic, apiv2.http(apiv2.posts.read));
// ## Pages
router.get('/pages', mw.authenticatePublic, apiv2.http(apiv2.pages.browse));

View file

@ -77,29 +77,6 @@ describe('Public API', function () {
});
});
it('browse pages', function (done) {
request.get('/ghost/api/v2/content/pages/?client_id=ghost-admin&client_secret=not_available')
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Origin, Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.pages);
should.exist(jsonResponse.meta);
jsonResponse.pages.should.have.length(1);
done();
});
});
it('browse posts with basic filters', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:kitchen-sink,featured:true&include=tags'))
.expect('Content-Type', /json/)

View file

@ -0,0 +1,50 @@
const url = require('url');
const should = require('should');
const supertest = require('supertest');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../../core/server/config');
const ghost = testUtils.startGhost;
let request;
describe('Pages', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'client:trusted-domain');
});
});
afterEach(function () {
configUtils.restore();
});
it('browse pages', function () {
request.get(localUtils.API.getApiQuery('pages/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
res.headers.vary.should.eql('Origin, Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.pages);
should.exist(jsonResponse.meta);
jsonResponse.pages.should.have.length(1);
res.body.pages[0].slug.should.eql(testUtils.DataGenerator.Content.posts[5].slug);
const urlParts = url.parse(res.body.pages[0].url);
should.exist(urlParts.protocol);
should.exist(urlParts.host);
});
});
});

View file

@ -0,0 +1,484 @@
const should = require('should');
const supertest = require('supertest');
const _ = require('lodash');
const url = require('url');
const cheerio = require('cheerio');
const moment = require('moment');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../../core/server/config');
const ghost = testUtils.startGhost;
let request;
describe('Posts', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'client:trusted-domain');
});
});
afterEach(function () {
configUtils.restore();
});
it('browse posts', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Origin, Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
// Public API does not return drafts and pages
_.map(jsonResponse.posts, (post) => {
post.status.should.eql('published');
post.page.should.eql(false);
});
// Default order check
jsonResponse.posts[0].slug.should.eql('welcome');
jsonResponse.posts[6].slug.should.eql('themes');
// check meta response for this test
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(11);
jsonResponse.meta.pagination.hasOwnProperty('next').should.be.true();
jsonResponse.meta.pagination.hasOwnProperty('prev').should.be.true();
should.not.exist(jsonResponse.meta.pagination.next);
should.not.exist(jsonResponse.meta.pagination.prev);
// kitchen sink
res.body.posts[9].slug.should.eql(testUtils.DataGenerator.Content.posts[1].slug);
let urlParts = url.parse(res.body.posts[9].feature_image);
should.exist(urlParts.protocol);
should.exist(urlParts.host);
urlParts = url.parse(res.body.posts[9].url);
should.exist(urlParts.protocol);
should.exist(urlParts.host);
const $ = cheerio.load(res.body.posts[9].html);
urlParts = url.parse($('img').attr('src'));
should.exist(urlParts.protocol);
should.exist(urlParts.host);
done();
});
});
it('browse posts with basic filters', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:kitchen-sink,featured:true&include=tags'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
const ids = _.map(jsonResponse.posts, 'id');
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
// should content filtered data and order
jsonResponse.posts.should.have.length(4);
ids.should.eql([
testUtils.DataGenerator.Content.posts[4].id,
testUtils.DataGenerator.Content.posts[2].id,
testUtils.DataGenerator.Content.posts[1].id,
testUtils.DataGenerator.Content.posts[0].id,
]);
// API does not return drafts
jsonResponse.posts.forEach((post) => {
post.page.should.be.false();
post.status.should.eql('published');
});
// Each post must either be featured or have the tag 'kitchen-sink'
_.each(jsonResponse.posts, (post) => {
if (post.featured) {
post.featured.should.equal(true);
} else {
const tags = _.map(post.tags, 'slug');
tags.should.containEql('kitchen-sink');
}
});
// The meta object should contain the right detail
jsonResponse.meta.should.have.property('pagination');
jsonResponse.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(4);
should.equal(jsonResponse.meta.pagination.next, null);
should.equal(jsonResponse.meta.pagination.prev, null);
done();
});
});
it('browse posts with basic page filter should not return pages', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=page:true'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.forEach((post) => {
post.page.should.be.false();
});
done();
});
});
it('browse posts with author filter', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=authors:[joe-bloggs,pat,ghost]&include=authors'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
const ids = _.map(jsonResponse.posts, 'id');
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
// 2. The data part of the response should be correct
// We should have 2 matching items
jsonResponse.posts.should.be.an.Array().with.lengthOf(11);
// Each post must either have the author 'joe-bloggs' or 'ghost', 'pat' is non existing author
const authors = _.map(jsonResponse.posts, function (post) {
return post.primary_author.slug;
});
authors.should.matchAny(/joe-bloggs|ghost'/);
done();
});
});
it('browse posts with published and draft status, should not return drafts', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=status:published,status:draft'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
jsonResponse.posts.should.be.an.Array().with.lengthOf(11);
jsonResponse.posts.forEach((post) => {
post.page.should.be.false();
post.status.should.eql('published');
});
done();
});
});
it('[deprecated] browse posts with page non matching filter', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:no-posts'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
jsonResponse.posts.should.be.an.Array().with.lengthOf(0);
jsonResponse.meta.should.have.property('pagination');
jsonResponse.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(0);
should.equal(jsonResponse.meta.pagination.next, null);
should.equal(jsonResponse.meta.pagination.prev, null);
done();
});
});
it('browse posts: request to include tags and authors should always contain absolute urls', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&include=tags,authors'))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.exist(res.body.posts);
// kitchen sink
res.body.posts[9].slug.should.eql(testUtils.DataGenerator.Content.posts[1].slug);
should.exist(res.body.posts[9].tags);
should.exist(res.body.posts[9].tags[0].url);
should.exist(url.parse(res.body.posts[9].tags[0].url).protocol);
should.exist(url.parse(res.body.posts[9].tags[0].url).host);
should.exist(res.body.posts[9].primary_tag);
should.exist(res.body.posts[9].primary_tag.url);
should.exist(url.parse(res.body.posts[9].primary_tag.url).protocol);
should.exist(url.parse(res.body.posts[9].primary_tag.url).host);
should.exist(res.body.posts[9].authors);
should.exist(res.body.posts[9].authors[0].url);
should.exist(url.parse(res.body.posts[9].authors[0].url).protocol);
should.exist(url.parse(res.body.posts[9].authors[0].url).host);
should.exist(res.body.posts[9].primary_author);
should.exist(res.body.posts[9].primary_author.url);
should.exist(url.parse(res.body.posts[9].primary_author.url).protocol);
should.exist(url.parse(res.body.posts[9].primary_author.url).host);
done();
});
});
it('browse posts from different origin', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-test&client_secret=not_available'))
.set('Origin', 'https://example.com')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Origin, Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
done();
});
});
it('ensure origin header on redirect is not getting lost', function (done) {
// NOTE: force a redirect to the admin url
configUtils.set('admin:url', 'http://localhost:9999');
request.get(localUtils.API.getApiQuery('posts?client_id=ghost-test&client_secret=not_available'))
.set('Origin', 'https://example.com')
// 301 Redirects _should_ be cached
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Origin, Accept, Accept-Encoding');
res.headers.location.should.eql('http://localhost:9999/ghost/api/v2/content/posts/?client_id=ghost-test&client_secret=not_available');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
done();
});
});
it('browse posts, ignores staticPages', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&staticPages=true'))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
done();
});
});
it('denies access with invalid client_secret', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=invalid_secret'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType', 'context']);
done();
});
});
it('denies access with invalid client_id', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=invalid-id&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType', 'context']);
done();
});
});
it('does not send CORS headers on an invalid origin', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', 'http://invalid-origin')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
should.not.exist(res.headers['access-control-allow-origin']);
done();
});
});
it('fetch the most recent post, then the prev, then the next should match the first', function (done) {
function createFilter(publishedAt, op) {
// This line deliberately uses double quotes because GQL cannot handle either double quotes
// or escaped singles, see TryGhost/GQL#34
return encodeURIComponent("published_at:" + op + "'" + publishedAt + "'");
}
request
.get(localUtils.API.getApiQuery(
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1'
))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then(function (res) {
should.exist(res.body.posts[0]);
var post = res.body.posts[0],
publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');
post.title.should.eql('Welcome to Ghost');
return request
.get(localUtils.API.getApiQuery(
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
+ createFilter(publishedAt, '<')
))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then(function (res) {
should.exist(res.body.posts[0]);
var post = res.body.posts[0],
publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');
post.title.should.eql('Writing posts with Ghost ✍️');
return request
.get(localUtils.API.getApiQuery(
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
+ createFilter(publishedAt, '>')
))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then(function (res) {
should.exist(res.body.posts[0]);
var post = res.body.posts[0];
post.title.should.eql('Welcome to Ghost');
done();
})
.catch(done);
});
});

View file

@ -46,6 +46,7 @@ describe('Unit: api/shared/http', function () {
apiImpl.args[0][0].options.should.eql({
context: {
api_key_id: null,
client_id: null,
user: null
}
});

View file

@ -0,0 +1,78 @@
const should = require('should');
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
describe('Unit: v2/utils/serializers/input/posts', function () {
it('default', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
client_id: 1
}
}
};
serializers.input.posts.all(apiConfig, frame);
frame.options.filter.should.eql('page:false');
});
it('should not work for non public context', function () {
const apiConfig = {};
const frame = {
options: {
}
};
serializers.input.posts.all(apiConfig, frame);
should.equal(frame.options.filter, undefined);
});
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
client_id: 1
},
filter: 'status:published+tag:eins'
}
};
serializers.input.posts.all(apiConfig, frame);
frame.options.filter.should.eql('status:published+tag:eins+page:false');
});
it('remove existing page filter', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
client_id: 1
},
filter: 'page:true+tag:eins'
}
};
serializers.input.posts.all(apiConfig, frame);
frame.options.filter.should.eql('tag:eins+page:false');
});
it('remove existing page filter', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
client_id: 1
},
filter: 'page:true'
}
};
serializers.input.posts.all(apiConfig, frame);
frame.options.filter.should.eql('page:false');
});
});