Ghost/ghost/core/test/e2e-api/admin/posts-bulk.test.js
Fabien 'egg' O'Carroll 9dde39b2a4
Handled CollectionPost relations manually (#18081)
refs https://github.com/TryGhost/Arch/issues/86

bookshelf-relations was generating tonnes of select queries from the
posts table in order to update the relations. We've instead implemented
this ourselves, so as to avoid the superfluous fetches. Working closer to
the db like this is nice, and makes you think more about performance.

This logic could be pulled out into a util (not bookshelf plugin) where
it could be used explicitly, but with the complexity hidden, we'll see ig.
2023-09-13 14:16:22 +07:00

363 lines
16 KiB
JavaScript

const DomainEvents = require('@tryghost/domain-events');
const {agentProvider, fixtureManager, mockManager} = require('../../utils/e2e-framework');
const models = require('../../../core/server/models');
const assert = require('assert/strict');
describe('Posts Bulk API', function () {
let agent;
before(async function () {
mockManager.mockLabsEnabled('collections');
agent = await agentProvider.getAdminAPIAgent();
// Note that we generate lots of fixtures here to test the bulk deletion correctly
await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'emails', 'redirects', 'clicks', 'comments', 'feedback', 'links', 'mentions');
await agent.loginAsOwner();
});
afterEach(function () {
mockManager.restore();
});
describe('Edit', function () {
it('Can feature multiple posts', async function () {
const filter = 'status:[published,draft,scheduled,sent]';
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
let featuredCollection = await models.Collection.findPage({filter: 'slug:featured', limit: 1, withRelated: ['collectionPosts']});
let featuredCollectionPostsAmount = featuredCollection.data[0].toJSON().collectionPosts.length;
assert(featuredCollectionPostsAmount > 0, 'Expect to have multiple featured collection posts');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'feature'
}
})
.expectStatus(200)
.matchBodySnapshot();
await DomainEvents.allSettled();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed`);
// Fetch all posts and check if they are featured
const posts = await models.Post.findAll({filter, status: 'all'});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
featuredCollection = await models.Collection.findPage({filter: 'slug:featured', limit: 1, withRelated: ['collectionPosts']});
featuredCollectionPostsAmount = featuredCollection.data[0].toJSON().collectionPosts.length;
assert.equal(featuredCollectionPostsAmount, amount, 'Expect to have same amount featured collection posts as changed');
for (const post of posts) {
assert(post.get('featured') === true, `Expect post ${post.id} to be featured`);
}
});
it('Can unfeature multiple posts', async function () {
const filter = 'status:[published,draft,scheduled,sent]';
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
let featuredCollection = await models.Collection.findPage({filter: 'slug:featured', limit: 1, withRelated: ['collectionPosts']});
let featuredCollectionPostsAmount = featuredCollection.data[0].toJSON().collectionPosts.length;
assert(featuredCollectionPostsAmount > 0, 'Expect to have multiple featured collection posts');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'unfeature'
}
})
.expectStatus(200)
.matchBodySnapshot();
await DomainEvents.allSettled();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed`);
// Fetch all posts and check if they are featured
const posts = await models.Post.findAll({filter, status: 'all'});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
featuredCollection = await models.Collection.findPage({filter: 'slug:featured', limit: 1, withRelated: ['collectionPosts']});
featuredCollectionPostsAmount = featuredCollection.data[0].toJSON().collectionPosts.length;
assert.equal(featuredCollectionPostsAmount, 0, 'Expect to have no featured collection posts');
for (const post of posts) {
assert(post.get('featured') === false, `Expect post ${post.id} to be unfeatured`);
}
});
it('Can change access of posts', async function () {
const filter = 'status:[published,draft,scheduled,sent]';
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'access',
meta: {
visibility: 'paid'
}
}
})
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed`);
// Fetch all posts and check if they have the correct access
const posts = await models.Post.findAll({filter, status: 'all'});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
for (const post of posts) {
assert(post.get('visibility') === 'paid', `Expect post ${post.id} to have access 'paid'`);
}
});
it('Can change access of posts to tiers', async function () {
const filter = 'status:[published,draft,scheduled,sent]';
const products = await models.Product.findAll();
const tier1 = products.models[0];
const tier2 = products.models[1];
assert(tier1.id && tier2.id);
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'access',
meta: {
visibility: 'tiers',
tiers: [
{
id: tier1.id
},
{
id: tier2.id
}
]
}
}
})
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed`);
// Fetch all posts and check if they have the correct access
const posts = await models.Post.findAll({filter, status: 'all', withRelated: ['tiers']});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
for (const post of posts) {
assert(post.get('visibility') === 'tiers', `Expect post ${post.id} to have access 'tiers'`);
assert.equal(post.related('tiers').length, 2);
}
});
it('Can add a single tag to posts', async function () {
const filter = 'status:[published]';
const tag = await models.Tag.findOne({slug: fixtureManager.get('tags', 0).slug});
assert(tag);
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'addTag',
meta: {
tags: [
{
id: tag.id
}
]
}
}
})
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed, got ${response.body.bulk.meta.stats.successful} instead`);
// Fetch all posts and check if they have the tag
const posts = await models.Post.findAll({filter, status: 'all', withRelated: ['tags']});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
for (const post of posts) {
const tags = post.related('tags');
// Check tag is in the list
assert(tags.find(t => t.id === tag.id), `Expect post ${post.id} to have tag ${tag.id}`);
}
});
it('Can add multiple tags to posts and create new tags', async function () {
const filter = 'status:[draft]';
const tag = await models.Tag.findOne({id: fixtureManager.get('tags', 1).id});
assert(tag);
const newTag = {
name: 'Just a random new tag'
};
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'addTag',
meta: {
tags: [
{
id: tag.id
},
{
name: newTag.name
}
]
}
}
})
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be changed, got ${response.body.bulk.meta.stats.successful} instead`);
// Check if the new tag was created
const newTags = await models.Tag.findAll({filter: `name:'${newTag.name}'`});
assert.equal(newTags.length, 1, `Expect tag to be created`);
const newTagModel = newTags.models[0];
// Fetch all posts and check if they have the tag
const posts = await models.Post.findAll({filter, status: 'all', withRelated: ['tags']});
assert.equal(posts.length, amount, `Expect all matching posts (${amount}) to be changed`);
for (const post of posts) {
const tags = post.related('tags');
// Check tag is in the list
assert(tags.find(t => t.id === tag.id), `Expect post ${post.id} to have tag ${tag.id}`);
assert(tags.find(t => t.id === newTagModel.id), `Expect post ${post.id} to have new tag ${newTagModel.id}`);
}
});
it('Can unpublish posts', async function () {
const filter = 'status:[published]';
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.put('/posts/bulk/?filter=' + encodeURIComponent(filter))
.body({
bulk: {
action: 'unpublish'
}
})
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be unpublished, got ${response.body.bulk.meta.stats.successful} instead`);
// Fetch all posts and check if they are unpublished
const posts = await models.Post.findAll({filter, status: 'all'});
assert.equal(posts.length, 0, `Expect all matching posts (${amount}) to be unpublished`);
});
});
describe('Delete', function () {
it('Can delete posts that match a tag', async function () {
const tag = await models.Tag.findOne({id: fixtureManager.get('tags', 0).id});
const filter = 'tag:' + tag.get('slug');
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
await agent
.get('posts/?collection=latest')
.expectStatus(200)
.expect((res) => {
assert(res.body.posts.length > 0, 'Expect latest collection to have some posts');
});
const response = await agent
.delete('/posts/?filter=' + encodeURIComponent(filter))
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be deleted, got ${response.body.bulk.meta.stats.successful} instead`);
// Check if all posts were deleted
const posts = await models.Post.findPage({filter, status: 'all'});
assert.equal(posts.meta.pagination.total, 0, `Expect all matching posts (${amount}) to be deleted`);
});
it('Can delete all posts', async function () {
const filter = 'status:[published,draft,scheduled,sent]';
// Check all the posts that should be affected
const changedPosts = await models.Post.findPage({filter, limit: 1, status: 'all'});
const amount = changedPosts.meta.pagination.total;
assert(amount > 0, 'Expect at least one post to be affected for this test to work');
const response = await agent
.delete('/posts/?filter=' + encodeURIComponent(filter))
.expectStatus(200)
.matchBodySnapshot();
assert.equal(response.body.bulk.meta.stats.successful, amount, `Expect all matching posts (${amount}) to be deleted, got ${response.body.bulk.meta.stats.successful} instead`);
// Check if all posts were deleted
const posts = await models.Post.findPage({filter, status: 'all'});
assert.equal(posts.meta.pagination.total, 0, `Expect all matching posts (${amount}) to be deleted`);
let latestCollection = await models.Collection.findPage({filter: 'slug:latest', limit: 1, withRelated: ['collectionPosts']});
latestCollection = latestCollection.data[0].toJSON().collectionPosts.length;
assert.equal(latestCollection, 0, 'Expect to have no collection posts');
});
});
});