Ghost/ghost/core/test/e2e-api/admin/posts.test.js
Naz 6bd2769146
Hardened tiers-related snapshot tests
refs https://github.com/TryGhost/Team/issues/2077

- Members and Posts test suites were using a broad tiers property matcher, which is an anti-pattern for snapshot tests. Without more specific snapshots it would be very hard to track down tier-related breaking changes!
- This change is groundwork for a refactor coming in tier usage at API's output serializers
2022-10-27 15:54:28 +08:00

352 lines
12 KiB
JavaScript

const should = require('should');
const assert = require('assert');
const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
const {anyArray, anyEtag, anyErrorId, anyLocationFor, anyObject, anyObjectId, anyISODateTime, anyString, anyStringNumber, anyUuid, stringMatching} = matchers;
const models = require('../../../core/server/models');
const tierSnapshot = {
id: anyObjectId,
created_at: anyISODateTime,
updated_at: anyISODateTime
};
const matchPostShallowIncludes = {
id: anyObjectId,
uuid: anyUuid,
comment_id: anyString,
url: anyString,
authors: anyArray,
primary_author: anyObject,
tags: anyArray,
primary_tag: anyObject,
tiers: Array(2).fill(tierSnapshot),
created_at: anyISODateTime,
updated_at: anyISODateTime,
published_at: anyISODateTime
};
const createLexical = (text) => {
return JSON.stringify({
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text,
type: 'text',
version: 1
}
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1
}
],
direction: 'ltr',
format: '',
indent: 0,
type: 'root',
version: 1
}
});
};
const createMobiledoc = (text) => {
return JSON.stringify({
version: '0.3.1',
ghostVersion: '4.0',
markups: [],
atoms: [],
cards: [],
sections: [
[1, 'p', [
[0, [], 0, text]
]]
]
});
};
describe('Posts API', function () {
let agent;
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('posts');
await agent.loginAsOwner();
});
afterEach(function () {
mockManager.restore();
});
it('Can browse', async function () {
const res = await agent.get('posts/?limit=2')
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
posts: new Array(2).fill(matchPostShallowIncludes)
});
});
it('Can browse with formats', async function () {
const res = await agent.get('posts/?formats=mobiledoc,lexical,html,plaintext&limit=2')
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
posts: new Array(2).fill(matchPostShallowIncludes)
});
});
describe('Create', function () {
it('Can create a post with mobiledoc', async function () {
const post = {
title: 'Mobiledoc test',
mobiledoc: createMobiledoc('Testing post creation with mobiledoc'),
lexical: null
};
await agent
.post('/posts/?formats=mobiledoc,lexical,html')
.body({posts: [post]})
.expectStatus(201)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('posts')
});
});
it('Can create a post with lexical', async function () {
const lexical = createLexical('Testing post creation with lexical');
const post = {
title: 'Lexical test',
mobiledoc: null,
lexical
};
const {body} = await agent
.post('/posts/?formats=mobiledoc,lexical,html')
.body({posts: [post]})
.expectStatus(201)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('posts')
});
const [postResponse] = body.posts;
// post revision is created
const postRevisions = await models.PostRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
postRevisions.length.should.equal(1);
postRevisions.at(0).get('lexical').should.equal(lexical);
// mobiledoc revision is not created
const mobiledocRevisions = await models.MobiledocRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
mobiledocRevisions.length.should.equal(0);
});
it('Errors if both mobiledoc and lexical are present', async function () {
const post = {
title: 'Mobiledoc+lexical test',
mobiledoc: createMobiledoc('Testing post creation with mobiledoc'),
lexical: createLexical('Testing post creation with lexical')
};
await agent
.post('/posts/?formats=mobiledoc,lexical')
.body({posts: [post]})
.expectStatus(422)
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
})
.matchHeaderSnapshot({
etag: anyEtag
});
});
it('Errors with an invalid lexical state object', async function () {
const post = {
title: 'Invalid lexical state',
lexical: JSON.stringify({
notLexical: true
})
};
await agent
.post('/posts/?formats=mobiledoc,lexical,html')
.body({posts: [post]})
.expectStatus(422)
.matchBodySnapshot({
errors: [{
id: anyErrorId,
context: stringMatching(/Invalid lexical structure\..*/)
}]
})
.matchHeaderSnapshot({
etag: anyEtag,
'content-length': anyStringNumber
});
});
});
describe('Update', function () {
it('Can update a post with mobiledoc', async function () {
const originalMobiledoc = createMobiledoc('Original text');
const updatedMobiledoc = createMobiledoc('Updated text');
const {body: postBody} = await agent
.post('/posts/?formats=mobiledoc,lexical,html')
.body({posts: [{
title: 'Mobiledoc update test',
mobiledoc: originalMobiledoc
}]})
.expectStatus(201)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('posts')
});
const [postResponse] = postBody.posts;
await agent
.put(`/posts/${postResponse.id}/?formats=mobiledoc,lexical,html`)
.body({posts: [Object.assign({}, postResponse, {mobiledoc: updatedMobiledoc})]})
.expectStatus(200)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
'x-cache-invalidate': anyString
});
// mobiledoc revisions are created
const mobiledocRevisions = await models.MobiledocRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
mobiledocRevisions.length.should.equal(2);
mobiledocRevisions.at(0).get('mobiledoc').should.equal(updatedMobiledoc);
mobiledocRevisions.at(1).get('mobiledoc').should.equal(originalMobiledoc);
// post revisions are not created
const postRevisions = await models.PostRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
postRevisions.length.should.equal(0);
});
it('Can update a post with lexical', async function () {
const originalLexical = createLexical('Original text');
const updatedLexical = createLexical('Updated text');
const {body: postBody} = await agent
.post('/posts/?formats=mobiledoc,lexical,html')
.body({posts: [{
title: 'Lexical update test',
lexical: originalLexical
}]})
.expectStatus(201)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('posts')
});
const [postResponse] = postBody.posts;
await agent
.put(`/posts/${postResponse.id}/?formats=mobiledoc,lexical,html`)
.body({posts: [Object.assign({}, postResponse, {lexical: updatedLexical})]})
.expectStatus(200)
.matchBodySnapshot({
posts: [Object.assign(matchPostShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
etag: anyEtag,
'x-cache-invalidate': anyString
});
// post revisions are created
const postRevisions = await models.PostRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
postRevisions.length.should.equal(2);
postRevisions.at(0).get('lexical').should.equal(updatedLexical);
postRevisions.at(1).get('lexical').should.equal(originalLexical);
// mobiledoc revisions are not created
const mobiledocRevisions = await models.MobiledocRevision
.where('post_id', postResponse.id)
.orderBy('created_at_ts', 'desc')
.fetchAll();
mobiledocRevisions.length.should.equal(0);
});
});
describe('Delete', function () {
it('Can destroy a post', async function () {
await agent
.delete(`posts/${fixtureManager.get('posts', 0).id}/`)
.expectStatus(204)
.expectEmptyBody()
.matchHeaderSnapshot({
etag: anyEtag
});
});
it('Cannot delete a non-existent posts', async function () {
// This error message from the API is not really what I would expect
// Adding this as a guard to demonstrate how future refactoring improves the output
await agent
.delete('/posts/abcd1234abcd1234abcd1234/')
.expectStatus(404)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
});
});