Fixed flaky test behavior for bulk actions

refs https://ghost.slack.com/archives/C02G9E68C/p1692816097875899

- With introduction of extra e2e test coverage for Collections some tests started to fail at random. The root issue here was the transaction processing collections was started before the original bulk action (bulk edit, bulk publish/unpublish, etc.) was fully committed. The full transaction commit happens with the bulkAction method return inside of `if (!options.transacting) {` block.
This commit is contained in:
Naz 2023-08-24 15:07:49 +08:00 committed by naz
parent 08d6425a53
commit 46f8c15448
2 changed files with 38 additions and 26 deletions

View File

@ -352,6 +352,10 @@ describe('Posts Bulk API', function () {
// 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: ['posts']});
latestCollection = latestCollection.data[0].toJSON().posts.length;
assert.equal(latestCollection, 0, 'Expect to have no collection posts');
});
});
});

View File

@ -224,13 +224,22 @@ class PostsService {
async bulkEdit(data, options) {
if (data.action === 'unpublish') {
return await this.#updatePosts({status: 'draft'}, {filter: this.#mergeFilters('status:published', options.filter), context: options.context, actionName: 'unpublished'});
const updateResult = await this.#updatePosts({status: 'draft'}, {filter: this.#mergeFilters('status:published', options.filter), context: options.context, actionName: 'unpublished'});
DomainEvents.dispatch(PostsBulkUnpublishedEvent.create(updateResult.editIds));
return updateResult;
}
if (data.action === 'feature') {
return await this.#updatePosts({featured: true}, {filter: options.filter, context: options.context, actionName: 'featured'});
const updateResult = await this.#updatePosts({featured: true}, {filter: options.filter, context: options.context, actionName: 'featured'});
DomainEvents.dispatch(PostsBulkFeaturedEvent.create(updateResult.editIds));
return updateResult;
}
if (data.action === 'unfeature') {
return await this.#updatePosts({featured: false}, {filter: options.filter, context: options.context, actionName: 'unfeatured'});
const updateResult = await this.#updatePosts({featured: false}, {filter: options.filter, context: options.context, actionName: 'unfeatured'});
DomainEvents.dispatch(PostsBulkUnfeaturedEvent.create(updateResult.editIds));
return updateResult;
}
if (data.action === 'access') {
if (!['public', 'members', 'paid', 'tiers'].includes(data.meta.visibility)) {
@ -267,7 +276,11 @@ class PostsService {
});
}
}
return await this.#bulkAddTags({tags: data.meta.tags}, {filter: options.filter, context: options.context});
const bulkResult = await this.#bulkAddTags({tags: data.meta.tags}, {filter: options.filter, context: options.context});
DomainEvents.dispatch(PostsBulkAddTagsEvent.create(bulkResult.editIds));
return bulkResult;
}
throw new errors.IncorrectUsageError({
message: tpl(messages.unsupportedBulkAction)
@ -281,6 +294,7 @@ class PostsService {
* @param {string} options.filter - An NQL Filter
* @param {object} options.context
* @param {object} [options.transacting]
* @returns {Promise<{successful: number, unsuccessful: number, editIds: string[]}>}
*/
async #bulkAddTags(data, options) {
if (!options.transacting) {
@ -320,16 +334,19 @@ class PostsService {
await options.transacting('posts_tags').insert(postTags);
await this.models.Post.addActions('edited', postRows.map(p => p.id), options);
const event = PostsBulkAddTagsEvent.create(postTags.map(pt => pt.post_id));
DomainEvents.dispatch(event);
return {
editIds: postRows.map(p => p.id),
successful: postRows.length,
unsuccessful: 0
};
}
async bulkDestroy(options) {
/**
*
* @param {Object} options
* @returns Promise<{successful: number, unsuccessful: number, deleteIds: string[]}>
*/
async #bulkDestroy(options) {
if (!options.transacting) {
return await this.models.Post.transaction(async (transacting) => {
return await this.bulkDestroy({
@ -398,8 +415,14 @@ class PostsService {
await this.models.Post.bulkDestroy(deleteEmailIds, 'emails', {transacting: options.transacting, throwErrors: true});
const result = await this.models.Post.bulkDestroy(deleteIds, 'posts', {...options, throwErrors: true});
const event = PostsBulkDestroyedEvent.create(deleteIds);
DomainEvents.dispatch(event);
result.deleteIds = deleteIds;
return result;
}
async bulkDestroy(options) {
const result = await this.#bulkDestroy(options);
DomainEvents.dispatch(PostsBulkDestroyedEvent.create(result.deleteIds));
return result;
}
@ -467,22 +490,7 @@ class PostsService {
});
}
if (options.actionName) {
let bulkActionEvent;
switch (options.actionName) {
case 'unpublished':
bulkActionEvent = PostsBulkUnpublishedEvent.create(editIds);
break;
case 'featured':
bulkActionEvent = PostsBulkFeaturedEvent.create(editIds);
break;
case 'unfeatured':
bulkActionEvent = PostsBulkUnfeaturedEvent.create(editIds);
break;
}
DomainEvents.dispatch(bulkActionEvent);
}
result.editIds = editIds;
return result;
}