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:
parent
981ad28283
commit
76f4a4bb03
9 changed files with 717 additions and 29 deletions
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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/)
|
||||
|
|
50
core/test/functional/api/v2/content/pages_spec.js
Normal file
50
core/test/functional/api/v2/content/pages_spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
484
core/test/functional/api/v2/content/posts_spec.js
Normal file
484
core/test/functional/api/v2/content/posts_spec.js
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
78
core/test/unit/api/v2/utils/serializers/input/posts_spec.js
Normal file
78
core/test/unit/api/v2/utils/serializers/input/posts_spec.js
Normal 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');
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue