Ghost/test/api-acceptance/content/posts_spec.js
Hannah Wolfe fdc043227a Updated fixtures for Ghost v4.0
- With 4.0 we have a brand new version of Casper, new fixtures and new default settings
- Fixture posts cover the key features and give users an introduction to how to use their site
- This all comes from the marketing and design teams to refresh the look and feel of Ghost and give users the best possible onboarding experience
Note: this fixture overhaul includes
- new content for new 4.0 features
- regenerated post content using our updated mobiledoc structure
- a switch from British to US English
2021-03-10 17:37:16 +00:00

292 lines
13 KiB
JavaScript

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 configUtils = require('../../utils/configUtils');
const config = require('../../../core/shared/config');
const localUtils = require('./utils');
describe('Posts Content API', function () {
let request;
before(async function () {
await testUtils.startGhost();
request = supertest.agent(config.get('url'));
await testUtils.initFixtures('owner:post', 'users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
});
afterEach(function () {
configUtils.restore();
});
const validKey = localUtils.getValidKey();
it('Can request posts', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
res.headers.vary.should.eql('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.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
// Default order 'published_at desc' check
jsonResponse.posts[0].slug.should.eql('welcome');
jsonResponse.posts[6].slug.should.eql('integrations');
// 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);
res.body.posts[7].slug.should.eql('not-so-short-bit-complex');
res.body.posts[7].html.should.match(/<a href="http:\/\/127.0.0.1:2369\/about#nowhere" title="Relative URL/);
res.body.posts[9].slug.should.eql('ghostly-kitchen-sink');
res.body.posts[9].html.should.match(/<img src="http:\/\/127.0.0.1:2369\/content\/images\/lol.jpg"/);
});
it('Can filter posts by tag', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=tag:kitchen-sink,featured:true&include=tags`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const jsonResponse = res.body;
const ids = _.map(jsonResponse.posts, 'id');
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
localUtils.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
]);
// 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);
});
it('Can filter posts by authors', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=authors:[joe-bloggs,pat,ghost,slimer-mcectoplasm]&include=authors`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const jsonResponse = res.body;
const ids = _.map(jsonResponse.posts, 'id');
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
localUtils.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);
// The API orders by number of matched authors.
jsonResponse.posts[0].slug.should.eql('not-so-short-bit-complex');
// Each post must either have the author 'joe-bloggs' or 'ghost', 'pat' is non existing author
const primaryAuthors = _.map(jsonResponse.posts, function (post) {
return post.primary_author.slug;
});
primaryAuthors.should.matchAny(/joe-bloggs|ghost'/);
_.filter(primaryAuthors, (value) => {
return value === 'ghost';
}).length.should.eql(7);
_.filter(primaryAuthors, (value) => {
return value === 'joe-bloggs';
}).length.should.eql(4);
});
it('Can request fields of posts', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&fields=url`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post', false, false, ['url']);
res.body.posts[0].url.should.eql('http://127.0.0.1:2369/welcome/');
});
it('Can include relations', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&include=tags,authors`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
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);
});
it('Can request posts from different origin', async function () {
const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
.set('Origin', 'https://example.com')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
res.headers.vary.should.eql('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.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
});
it('Can filter by published date', async function () {
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 + '\'');
}
const res = await request
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.exist(res.body.posts[0]);
const post = res.body.posts[0];
const publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');
post.title.should.eql('Start here for a quick overview of everything you need to know');
const res2 = await request
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt, `<`)}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.exist(res2.body.posts[0]);
const post2 = res2.body.posts[0];
const publishedAt2 = moment(post2.published_at).format('YYYY-MM-DD HH:mm:ss');
post2.title.should.eql('Customizing your brand and design settings');
const res3 = await request
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt2, `>`)}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.exist(res3.body.posts[0]);
const post3 = res3.body.posts[0];
post3.title.should.eql('Start here for a quick overview of everything you need to know');
});
it('Can request a single post', async function () {
const res = await request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
should.not.exist(jsonResponse.meta);
jsonResponse.posts.should.have.length(1);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
});
});