Improved newsletter limit checking (#14780)

refs https://github.com/TryGhost/Team/issues/1583

- Check limits when unarchiving newsletters
- Added tests for more scenarios
- When editing/adding newsletters, the limit check happens in the same transaction.
- `limit-service` was bumped to add transactions support
- Added transaction support for edit in newsletter service
This commit is contained in:
Simon Backx 2022-05-12 14:28:45 +02:00 committed by GitHub
parent 3398e0d07d
commit 3214186f98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 557 additions and 11 deletions

View File

@ -112,7 +112,8 @@ class NewslettersService {
* @public
* @param {object} attrs model properties
* @param {Object} [options] options
* @param {Object} [options] options.transacting
* @param {boolean} [options.opt_in_existing] Opt in existing members
* @param {Object} [options.transacting]
* @returns {Promise<{object}>} Newsetter Model with verification metadata
*/
async add(attrs, options = {}) {
@ -124,7 +125,9 @@ class NewslettersService {
});
}
await this.limitService.errorIfWouldGoOverLimit('newsletters');
if (!attrs.status || attrs.status === 'active') {
await this.limitService.errorIfWouldGoOverLimit('newsletters', options.transacting ? {transacting: options.transacting} : {});
}
// remove any email properties that are not allowed to be set without verification
const {cleanedAttrs, emailsToVerify} = await this.prepAttrsForEmailVerification(attrs);
@ -177,14 +180,21 @@ class NewslettersService {
* @param {object} attrs model properties
* @param {Object} options options
* @param {string} options.id Newsletter id to edit
* @param {Object} [options.transacting]
* @returns {Promise<{object}>} Newsetter Model with verification metadata
*/
async edit(attrs, options) {
const sharedOptions = _.pick(options, 'transacting');
// fetch newsletter first so we can compare changed emails
const originalNewsletter = await this.NewsletterModel.findOne({id: options.id}, {require: true});
const originalNewsletter = await this.NewsletterModel.findOne({id: options.id}, {...sharedOptions, require: true});
const {cleanedAttrs, emailsToVerify} = await this.prepAttrsForEmailVerification(attrs, originalNewsletter);
if (originalNewsletter.status !== 'active' && cleanedAttrs.status === 'active') {
await this.limitService.errorIfWouldGoOverLimit('newsletters', sharedOptions);
}
let updatedNewsletter;
try {
@ -307,7 +317,7 @@ class NewslettersService {
/**
* @typedef {object} ILimitService
* @prop {(name: string) => Promise<void>} errorIfWouldGoOverLimit
* @prop {(name: string, options?: {transacting?: Object}) => Promise<void>} errorIfWouldGoOverLimit
**/
module.exports = NewslettersService;

View File

@ -81,7 +81,7 @@
"@tryghost/kg-default-cards": "5.16.2",
"@tryghost/kg-markdown-html-renderer": "5.1.5",
"@tryghost/kg-mobiledoc-html-renderer": "5.3.5",
"@tryghost/limit-service": "1.1.2",
"@tryghost/limit-service": "1.2.0",
"@tryghost/logging": "2.1.8",
"@tryghost/magic-link": "1.0.25",
"@tryghost/member-events": "0.4.5",

View File

@ -1062,6 +1062,360 @@ Object {
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding a newsletter now doesn't fail 1: [body] 1`] = `
Object {
"meta": Object {
"opted_in_member_count": 6,
},
"newsletters": Array [
Object {
"body_font_category": "sans_serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Naughty newsletter",
"sender_email": null,
"sender_name": null,
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "naughty-newsletter",
"sort_order": 3,
"status": "active",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "sans_serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding a newsletter now doesn't fail 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "695",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/newsletters\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding an archived newsletter doesn't fail 1: [body] 1`] = `
Object {
"meta": Object {
"opted_in_member_count": 6,
},
"newsletters": Array [
Object {
"body_font_category": "sans_serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Archived newsletter",
"sender_email": null,
"sender_name": null,
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "archived-newsletter",
"sort_order": 3,
"status": "archived",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "sans_serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding an archived newsletter doesn't fail 2: [body] 1`] = `
Object {
"meta": Object {
"opted_in_member_count": 6,
},
"newsletters": Array [
Object {
"body_font_category": "sans_serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Archived newsletter",
"sender_email": null,
"sender_name": null,
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "archived-newsletter",
"sort_order": 3,
"status": "archived",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "sans_serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding an archived newsletter doesn't fail 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "699",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/newsletters\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding an archived newsletter doesn't fail 3: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "699",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/newsletters\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding newsletter fails 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": "Your plan supports up to 3 newsletters. Please upgrade to add more.",
"details": Object {
"limit": 3,
"name": "newsletters",
"total": 3,
},
"ghostErrorCode": null,
"help": "https://ghost.org/help/",
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "Host Limit error, cannot save newsletter.",
"property": null,
"type": "HostLimitError",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Adding newsletter fails without transaction 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": "Your plan supports up to 3 newsletters. Please upgrade to add more.",
"details": Object {
"limit": 3,
"name": "newsletters",
"total": 3,
},
"ghostErrorCode": null,
"help": "https://ghost.org/help/",
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "Host Limit error, cannot save newsletter.",
"property": null,
"type": "HostLimitError",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Archiving a newsletter doesn't fail 1: [body] 1`] = `
Object {
"newsletters": Array [
Object {
"body_font_category": "sans_serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sender_email": null,
"sender_name": null,
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "default-newsletter",
"sort_order": 0,
"status": "archived",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "sans_serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Archiving a newsletter doesn't fail 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "662",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Editing an archived newsletter doesn't fail 1: [body] 1`] = `
Object {
"newsletters": Array [
Object {
"body_font_category": "serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Updated archived newsletter name",
"sender_email": "jamie@example.com",
"sender_name": "Jamie",
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "old-newsletter",
"sort_order": 2,
"status": "archived",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Editing an archived newsletter doesn't fail 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "680",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Editing an archived newsletter doesn't fails 1: [body] 1`] = `
Object {
"newsletters": Array [
Object {
"body_font_category": "serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Updated archived newsletter name",
"sender_email": "jamie@example.com",
"sender_name": "Jamie",
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "old-newsletter",
"sort_order": 2,
"status": "archived",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Editing an archived newsletter doesn't fails 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "680",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Newsletters API Host Settings: newsletter limits Max limit Unarchiving a newsletter fails 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": "Your plan supports up to 3 newsletters. Please upgrade to add more.",
"details": Object {
"limit": 3,
"name": "newsletters",
"total": 3,
},
"ghostErrorCode": null,
"help": "https://ghost.org/help/",
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "Host Limit error, cannot edit newsletter.",
"property": null,
"type": "HostLimitError",
},
],
}
`;
exports[`Newsletters API Host Settings: newsletter limits Request fails when newsletter limit is in place 1: [body] 1`] = `
Object {
"errors": Array [

View File

@ -1,6 +1,7 @@
const assert = require('assert');
const {agentProvider, mockManager, fixtureManager, configUtils, dbUtils, matchers} = require('../../utils/e2e-framework');
const {anyEtag, anyObjectId, anyUuid, anyISODateTime, anyLocationFor, anyNumber} = matchers;
const models = require('../../../core/server/models');
const assertMemberRelationCount = async (newsletterId, expectedCount) => {
const relations = await dbUtils.knex('members_newsletters').where({newsletter_id: newsletterId}).pluck('id');
@ -358,10 +359,10 @@ describe('Newsletters API', function () {
});
describe('Host Settings: newsletter limits', function () {
afterEach(function () {
after(function () {
configUtils.set('hostSettings:limits', undefined);
});
it('Request fails when newsletter limit is in place', async function () {
configUtils.set('hostSettings:limits', {
newsletters: {
@ -388,6 +389,187 @@ describe('Newsletters API', function () {
}]
});
});
describe('Max limit', function () {
before(async function () {
configUtils.set('hostSettings:limits', {
newsletters: {
max: 3,
error: 'Your plan supports up to {{max}} newsletters. Please upgrade to add more.'
}
});
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('newsletters', 'members:newsletters');
await agent.loginAsOwner();
});
it('Adding newsletter fails', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const newsletter = {
name: 'Naughty newsletter'
};
await agent
.post(`newsletters/?opt_in_existing=true`)
.body({newsletters: [newsletter]})
.expectStatus(403)
.matchBodySnapshot({
errors: [{
id: anyUuid
}]
})
.expect(({body}) => {
assert.equal(body.errors[0].context, 'Your plan supports up to 3 newsletters. Please upgrade to add more.');
});
});
it('Adding newsletter fails without transaction', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const newsletter = {
name: 'Naughty newsletter'
};
// Note that ?opt_in_existing=true will trigger a transaction, so we explicitly test here without a
// transaction
await agent
.post(`newsletters/`)
.body({newsletters: [newsletter]})
.expectStatus(403)
.matchBodySnapshot({
errors: [{
id: anyUuid
}]
})
.expect(({body}) => {
assert.equal(body.errors[0].context, 'Your plan supports up to 3 newsletters. Please upgrade to add more.');
});
});
it('Adding an archived newsletter doesn\'t fail', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const newsletter = {
name: 'Archived newsletter',
status: 'archived'
};
await agent
.post(`newsletters/?opt_in_existing=true`)
.body({newsletters: [newsletter]})
.expectStatus(201)
.matchBodySnapshot({
newsletters: [newsletterSnapshot]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('newsletters')
});
});
it('Editing an archived newsletter doesn\'t fail', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const archivedNewsletter = allNewsletters.find(n => n.get('status') !== 'active');
assert.ok(archivedNewsletter, 'This test expects to have an archived newsletter in the test fixtures');
const id = archivedNewsletter.id;
await agent.put(`newsletters/${id}`)
.body({
newsletters: [{
name: 'Updated archived newsletter name'
}]
})
.expectStatus(200)
.matchBodySnapshot({
newsletters: [newsletterSnapshot]
})
.matchHeaderSnapshot({
etag: anyEtag
});
});
it('Unarchiving a newsletter fails', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const archivedNewsletter = allNewsletters.find(n => n.get('status') !== 'active');
assert.ok(archivedNewsletter, 'This test expects to have an archived newsletter in the test fixtures');
const id = archivedNewsletter.id;
await agent.put(`newsletters/${id}`)
.body({
newsletters: [{
status: 'active'
}]
})
.expectStatus(403)
.matchBodySnapshot({
errors: [{
id: anyUuid
}]
})
.expect(({body}) => {
assert.equal(body.errors[0].context, 'Your plan supports up to 3 newsletters. Please upgrade to add more.');
});
});
it('Archiving a newsletter doesn\'t fail', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 3, 'This test expects to have 3 current active newsletters');
const activeNewsletter = allNewsletters.find(n => n.get('status') === 'active');
const id = activeNewsletter.id;
await agent.put(`newsletters/${id}`)
.body({
newsletters: [{
status: 'archived'
}]
})
.expectStatus(200)
.matchBodySnapshot({
newsletters: [newsletterSnapshot]
})
.matchHeaderSnapshot({
etag: anyEtag
});
});
it('Adding a newsletter now doesn\'t fail', async function () {
const allNewsletters = await models.Newsletter.findAll();
const newsletterCount = allNewsletters.filter(n => n.get('status') === 'active').length;
assert.equal(newsletterCount, 2, 'This test expects to have 2 current active newsletters');
const newsletter = {
name: 'Naughty newsletter'
};
await agent
.post(`newsletters/?opt_in_existing=true`)
.body({newsletters: [newsletter]})
.expectStatus(201)
.matchBodySnapshot({
newsletters: [newsletterSnapshot]
})
.matchHeaderSnapshot({
etag: anyEtag,
location: anyLocationFor('newsletters')
});
});
});
});
it(`Can't add multiple newsletters with same name`, async function () {

View File

@ -2106,10 +2106,10 @@
dependencies:
semver "^7.3.5"
"@tryghost/limit-service@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tryghost/limit-service/-/limit-service-1.1.2.tgz#248f5b1c5383d302a6e29e8bcd2948ff9eb9ab4f"
integrity sha512-aOUVnNvhYDANqtx+2pS0c0RADG39QDQ5tB7CLPjpcz2B6Nsc1fg4dEJeXU8fI1JT0YpMDRSliiNRh0/MMkozbA==
"@tryghost/limit-service@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@tryghost/limit-service/-/limit-service-1.2.0.tgz#fea838bcb77e30b043f670c73237546bebfa4686"
integrity sha512-awO9ZANst25v5+udlkwAcVV3OivisiWEX4OcD4i1YlSqPqcc5ncYr0ozlNjMR3djY0gX+rfN+WUc20ybwWL0Xg==
dependencies:
"@tryghost/errors" "^1.2.1"
lodash "^4.17.21"