Added acceptance test for /member/:id/?include=email_recipients (#12477)

refs c1d66f0b01

- fixed base model allowing '@@INDEXES@@' as a permitted attribute/order
- fixed base model automatically setting `@@INDEXES@@` to null on the model when creating
- added `doAuth('members:emails')`
  - creates an `email_batch` record attached to the first email in the fixtures
  - creates an `email_recipients` record for each member
  - runs analytics aggregation so the email and member counts are as expected
- added acceptance test for `/member/:id/?include=email_recipients`
This commit is contained in:
Kevin Ansfield 2020-12-11 18:45:35 +00:00 committed by GitHub
parent 90adc9ed98
commit 8aa55feaf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 9 deletions

View File

@ -172,13 +172,15 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
// Ghost option handling - get permitted attributes from server/data/schema.js, where the DB schema is defined
permittedAttributes: function permittedAttributes() {
return _.keys(schema.tables[this.tableName]);
return _.keys(schema.tables[this.tableName])
.filter(key => key.indexOf('@@') === -1);
},
// Ghost ordering handling, allows to order by permitted attributes by default and can be overriden on specific model level
orderAttributes: function orderAttributes() {
return Object.keys(schema.tables[this.tableName])
.map(key => `${this.tableName}.${key}`);
.map(key => `${this.tableName}.${key}`)
.filter(key => key.indexOf('@@') === -1);
},
// When loading an instance, subclasses can specify default to fetch
@ -354,7 +356,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
*
* Happens after validation to ensure we don't set fields which are not nullable on db level.
*/
_.each(Object.keys(schema.tables[this.tableName]), (columnKey) => {
_.each(Object.keys(schema.tables[this.tableName]).filter(key => key.indexOf('@@') === -1), (columnKey) => {
if (model.get(columnKey) === undefined) {
model.set(columnKey, null);
}

View File

@ -20,7 +20,7 @@ describe('Members API', function () {
before(async function () {
await testUtils.startGhost();
request = supertest.agent(config.get('url'));
await localUtils.doAuth(request, 'members');
await localUtils.doAuth(request, 'members', 'members:emails');
sinon.stub(labs, 'isSet').withArgs('members').returns(true);
});
@ -123,6 +123,25 @@ describe('Members API', function () {
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
});
it('Can read and include email_recipients', async function () {
const res = await request
.get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/?include=email_recipients`))
.set('Origin', config.get('url'))
.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);
should.exist(jsonResponse.members);
jsonResponse.members.should.have.length(1);
localUtils.API.checkResponse(jsonResponse.members[0], 'member', ['stripe', 'email_recipients']);
jsonResponse.members[0].email_recipients.length.should.equal(1);
localUtils.API.checkResponse(jsonResponse.members[0].email_recipients[0], 'email_recipient', ['email']);
localUtils.API.checkResponse(jsonResponse.members[0].email_recipients[0].email, 'email');
});
it('Can add', async function () {
const member = {
name: 'test',

View File

@ -115,6 +115,9 @@ const expectedProperties = {
email: _(schema.emails)
.keys(),
email_preview: ['html', 'subject', 'plaintext'],
email_recipient: _(schema.email_recipients)
.keys()
.filter(key => key.indexOf('@@') === -1),
snippet: _(schema.snippets).keys()
};

View File

@ -310,23 +310,27 @@ DataGenerator.Content = {
{
id: ObjectId.generate(),
email: 'member1@test.com',
name: 'Mr Egg'
name: 'Mr Egg',
uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b340'
},
{
id: ObjectId.generate(),
email: 'member2@test.com',
email_open_rate: 50
email_open_rate: 50,
uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b341'
},
{
id: ObjectId.generate(),
email: 'paid@test.com',
name: 'Egon Spengler',
email_open_rate: 80
email_open_rate: 80,
uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b342'
},
{
id: ObjectId.generate(),
email: 'trialing@test.com',
name: 'Ray Stantz'
name: 'Ray Stantz',
uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b343'
}
],
@ -446,9 +450,11 @@ DataGenerator.Content = {
uuid: '6b6afda6-4b5e-4893-bff6-f16859e8349a',
status: 'submitted',
email_count: 2,
recipient_filter: 'all',
subject: 'You got mailed!',
html: '<p>Look! I\'m an email</p>',
plaintext: 'Waba-daba-dab-da',
track_opens: false,
submitted_at: moment().toDate()
},
{
@ -456,15 +462,72 @@ DataGenerator.Content = {
uuid: '365daa11-4bf0-4614-ad43-6346387ffa00',
status: 'failed',
error: 'Everything went south',
stats: '',
email_count: 3,
subject: 'You got mailed! Again!',
html: '<p>What\'s that? Another email!</p>',
plaintext: 'yes this is an email',
track_opens: false,
submitted_at: moment().toDate()
}
],
email_batches: [
{
id: ObjectId.generate(),
email_id: null, // emails[0] relation added later
// TODO: cleanup <> in provider_id
provider_id: '<email1@testing.mailgun.net>',
status: 'submitted'
}
],
email_recipients: [
{
id: ObjectId.generate(),
email_id: null, // emails[0] relation added later
member_id: null, // members[0] relation added later
batch_id: null, // email_batches[0] relation added later
processed_at: moment().toDate(),
failed_at: null,
member_uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b340',
member_email: 'member1@test.com',
member_name: 'Mr Egg'
},
{
id: ObjectId.generate(),
email_id: null, // emails[0] relation added later
member_id: null, // members[1] relation added later
batch_id: null, // email_batches[0] relation added later
processed_at: moment().toDate(),
failed_at: null,
member_uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b341',
member_email: 'member2@test.com',
member_name: null
},
{
id: ObjectId.generate(),
email_id: null, // emails[0] relation added later
member_id: null, // members[2] relation added later
batch_id: null, // email_batches[0] relation added later
processed_at: moment().toDate(),
failed_at: null,
member_uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b342',
member_email: 'member1@test.com',
member_name: 'Mr Egg'
},
{
id: ObjectId.generate(),
email_id: null, // emails[0] relation added later
member_id: null, // members[3] relation added later
batch_id: null, // email_batches[0] relation added later
processed_at: moment().toDate(),
failed_at: null,
member_uuid: 'f6f91461-d7d8-4a3f-aa5d-8e582c40b343',
member_email: 'member1@test.com',
member_name: 'Mr Egg'
}
],
snippets: [
{
id: ObjectId.generate(),
@ -480,6 +543,19 @@ DataGenerator.Content.api_keys[0].integration_id = DataGenerator.Content.integra
DataGenerator.Content.api_keys[1].integration_id = DataGenerator.Content.integrations[0].id;
DataGenerator.Content.emails[0].post_id = DataGenerator.Content.posts[0].id;
DataGenerator.Content.emails[1].post_id = DataGenerator.Content.posts[1].id;
DataGenerator.Content.email_batches[0].email_id = DataGenerator.Content.emails[0].id;
DataGenerator.Content.email_recipients[0].batch_id = DataGenerator.Content.email_batches[0].id;
DataGenerator.Content.email_recipients[0].email_id = DataGenerator.Content.email_batches[0].email_id;
DataGenerator.Content.email_recipients[0].member_id = DataGenerator.Content.members[0].id;
DataGenerator.Content.email_recipients[1].batch_id = DataGenerator.Content.email_batches[0].id;
DataGenerator.Content.email_recipients[1].email_id = DataGenerator.Content.email_batches[0].email_id;
DataGenerator.Content.email_recipients[1].member_id = DataGenerator.Content.members[1].id;
DataGenerator.Content.email_recipients[2].batch_id = DataGenerator.Content.email_batches[0].id;
DataGenerator.Content.email_recipients[2].email_id = DataGenerator.Content.email_batches[0].email_id;
DataGenerator.Content.email_recipients[2].member_id = DataGenerator.Content.members[2].id;
DataGenerator.Content.email_recipients[3].batch_id = DataGenerator.Content.email_batches[0].id;
DataGenerator.Content.email_recipients[3].email_id = DataGenerator.Content.email_batches[0].email_id;
DataGenerator.Content.email_recipients[3].member_id = DataGenerator.Content.members[3].id;
DataGenerator.Content.members_stripe_customers[0].member_id = DataGenerator.Content.members[2].id;
DataGenerator.Content.members_stripe_customers[1].member_id = DataGenerator.Content.members[3].id;
@ -758,6 +834,22 @@ DataGenerator.forKnex = (function () {
});
}
function createEmailBatch(overrides) {
const newObj = _.cloneDeep(overrides);
return _.defaults(newObj, {
id: ObjectId.generate(),
created_at: new Date(),
updated_at: new Date()
});
}
function createEmailRecipient(overrides) {
const newObj = _.cloneDeep(overrides);
return _.defaults(newObj, {
id: ObjectId.generate()
});
}
const posts = [
createPost(DataGenerator.Content.posts[0]),
createPost(DataGenerator.Content.posts[1]),
@ -965,6 +1057,17 @@ DataGenerator.forKnex = (function () {
createEmail(DataGenerator.Content.emails[1])
];
const email_batches = [
createEmailBatch(DataGenerator.Content.email_batches[0])
];
const email_recipients = [
createEmailRecipient(DataGenerator.Content.email_recipients[0]),
createEmailRecipient(DataGenerator.Content.email_recipients[1]),
createEmailRecipient(DataGenerator.Content.email_recipients[2]),
createEmailRecipient(DataGenerator.Content.email_recipients[3])
];
const members = [
createMember(DataGenerator.Content.members[0]),
createMember(DataGenerator.Content.members[1]),
@ -1035,6 +1138,8 @@ DataGenerator.forKnex = (function () {
integrations,
api_keys,
emails,
email_batches,
email_recipients,
labels,
members,
members_labels,

View File

@ -22,6 +22,7 @@ const routingService = require('../../core/frontend/services/routing');
const settingsService = require('../../core/server/services/settings');
const frontendSettingsService = require('../../core/frontend/services/settings');
const settingsCache = require('../../core/server/services/settings/cache');
const emailAnalyticsService = require('../../core/server/services/email-analytics');
const imageLib = require('../../core/server/lib/image');
const web = require('../../core/server/web');
const permissions = require('../../core/server/services/permissions');
@ -501,6 +502,27 @@ fixtures = {
});
},
insertEmailsAndRecipients: function insertEmailsAndRecipients() {
return Promise.each(_.cloneDeep(DataGenerator.forKnex.emails), function (email) {
return models.Email.add(email, module.exports.context.internal);
}).then(function () {
return Promise.each(_.cloneDeep(DataGenerator.forKnex.email_batches), function (emailBatch) {
return models.EmailBatch.add(emailBatch, module.exports.context.internal);
});
}).then(function () {
return Promise.each(_.cloneDeep(DataGenerator.forKnex.email_recipients), (emailRecipient) => {
return models.EmailRecipient.add(emailRecipient, module.exports.context.internal);
});
}).then(function () {
const toAggregate = {
emailIds: DataGenerator.forKnex.emails.map(email => email.id),
memberIds: DataGenerator.forKnex.members.map(member => member.id)
};
return emailAnalyticsService.aggregateStats(toAggregate);
});
},
insertSnippets: function insertSnippets() {
return Promise.map(DataGenerator.forKnex.snippets, function (snippet) {
return models.Snippet.add(snippet, module.exports.context.internal);
@ -576,6 +598,9 @@ toDoList = {
members: function insertMembersAndLabels() {
return fixtures.insertMembersAndLabels();
},
'members:emails': function insertEmailsAndRecipients() {
return fixtures.insertEmailsAndRecipients();
},
posts: function insertPostsAndTags() {
return fixtures.insertPostsAndTags();
},