mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-27 22:49:56 -05:00
Added 100% test coverage to EmailService class
refs https://github.com/TryGhost/Team/issues/2339 Tests the EmailService class of the email-service package.
This commit is contained in:
parent
45ab2586b0
commit
e2e9a56583
1 changed files with 358 additions and 0 deletions
358
ghost/email-service/test/email-service.test.js
Normal file
358
ghost/email-service/test/email-service.test.js
Normal file
|
@ -0,0 +1,358 @@
|
|||
const EmailService = require('../lib/email-service');
|
||||
const assert = require('assert');
|
||||
const ObjectId = require('bson-objectid').default;
|
||||
const sinon = require('sinon');
|
||||
|
||||
const createModel = (propertiesAndRelations) => {
|
||||
return {
|
||||
id: ObjectId().toHexString(),
|
||||
getLazyRelation: async (relation) => {
|
||||
return propertiesAndRelations[relation];
|
||||
},
|
||||
get: (property) => {
|
||||
return propertiesAndRelations[property];
|
||||
},
|
||||
save: (properties) => {
|
||||
Object.assign(propertiesAndRelations, properties);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const createModelClass = () => {
|
||||
return {
|
||||
add: async (properties) => {
|
||||
return Promise.resolve(createModel(properties));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
describe('Email Service', function () {
|
||||
let memberCount, limited, verificicationRequired, service;
|
||||
let scheduleEmail;
|
||||
let settings, settingsCache;
|
||||
let membersRepository;
|
||||
let emailRenderer;
|
||||
let sendingService;
|
||||
|
||||
beforeEach(function () {
|
||||
memberCount = 123;
|
||||
limited = {
|
||||
emails: null, // null = not limited, true = limited and error, false = limited no error
|
||||
members: null
|
||||
};
|
||||
verificicationRequired = false;
|
||||
scheduleEmail = sinon.stub().returns();
|
||||
settings = {};
|
||||
settingsCache = {
|
||||
get(key) {
|
||||
return settings[key];
|
||||
}
|
||||
};
|
||||
membersRepository = {
|
||||
get: sinon.stub().returns(undefined)
|
||||
};
|
||||
emailRenderer = {
|
||||
getSubject: () => {
|
||||
return 'Subject';
|
||||
},
|
||||
getFromAddress: () => {
|
||||
return 'From';
|
||||
},
|
||||
getReplyToAddress: () => {
|
||||
return 'ReplyTo';
|
||||
},
|
||||
renderBody: () => {
|
||||
return {
|
||||
html: 'HTML',
|
||||
plaintext: 'Plaintext',
|
||||
replacements: []
|
||||
};
|
||||
}
|
||||
};
|
||||
sendingService = {
|
||||
send: sinon.stub().returns()
|
||||
};
|
||||
|
||||
service = new EmailService({
|
||||
emailSegmenter: {
|
||||
getMembersCount: () => {
|
||||
return Promise.resolve(memberCount);
|
||||
}
|
||||
},
|
||||
limitService: {
|
||||
isLimited: (type) => {
|
||||
return typeof limited[type] === 'boolean';
|
||||
},
|
||||
errorIfIsOverLimit: (type) => {
|
||||
if (limited[type]) {
|
||||
throw new Error('Over limit');
|
||||
}
|
||||
},
|
||||
errorIfWouldGoOverLimit: (type) => {
|
||||
if (limited[type]) {
|
||||
throw new Error('Would go over limit');
|
||||
}
|
||||
}
|
||||
},
|
||||
verificationTrigger: {
|
||||
checkVerificationRequired: () => {
|
||||
return Promise.resolve(verificicationRequired);
|
||||
}
|
||||
},
|
||||
models: {
|
||||
Email: createModelClass()
|
||||
},
|
||||
batchSendingService: {
|
||||
scheduleEmail
|
||||
},
|
||||
settingsCache,
|
||||
emailRenderer,
|
||||
membersRepository,
|
||||
sendingService
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('checkLimits', function () {
|
||||
it('Throws if over member limit', async function () {
|
||||
limited.members = true;
|
||||
await assert.rejects(service.checkLimits(), /Over limit/);
|
||||
});
|
||||
|
||||
it('Throws if over email limit', async function () {
|
||||
limited.emails = true;
|
||||
await assert.rejects(service.checkLimits(), /Would go over limit/);
|
||||
});
|
||||
|
||||
it('Throws if verification is required', async function () {
|
||||
verificicationRequired = true;
|
||||
await assert.rejects(service.checkLimits(), /Email sending is temporarily disabled/);
|
||||
});
|
||||
|
||||
it('Does not throw if limits are enabled', async function () {
|
||||
// Enable limits, but don't go over limit
|
||||
limited.members = false;
|
||||
limited.emails = false;
|
||||
await assert.doesNotReject(service.checkLimits());
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEmail', function () {
|
||||
it('Throws if post does not have a newsletter', async function () {
|
||||
const post = createModel({
|
||||
newsletter: null
|
||||
});
|
||||
|
||||
await assert.rejects(service.createEmail(post), /The post does not have a newsletter relation/);
|
||||
});
|
||||
|
||||
it('Throws if post does not have an active newsletter', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'archived'
|
||||
})
|
||||
});
|
||||
|
||||
await assert.rejects(service.createEmail(post), /Cannot send email to archived newsletters/);
|
||||
});
|
||||
|
||||
it('Creates and schedules an email', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
}),
|
||||
mobiledoc: 'Mobiledoc'
|
||||
});
|
||||
|
||||
const email = await service.createEmail(post);
|
||||
sinon.assert.calledOnce(scheduleEmail);
|
||||
assert.strictEqual(email.get('feedback_enabled'), true);
|
||||
assert.strictEqual(email.get('newsletter_id'), post.get('newsletter').id);
|
||||
assert.strictEqual(email.get('post_id'), post.id);
|
||||
assert.strictEqual(email.get('status'), 'pending');
|
||||
assert.strictEqual(email.get('source'), post.get('mobiledoc'));
|
||||
assert.strictEqual(email.get('source_type'), 'mobiledoc');
|
||||
});
|
||||
|
||||
it('Creates and schedules an email with lexical', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
}),
|
||||
lexical: 'Lexical'
|
||||
});
|
||||
|
||||
const email = await service.createEmail(post);
|
||||
sinon.assert.calledOnce(scheduleEmail);
|
||||
assert.strictEqual(email.get('feedback_enabled'), true);
|
||||
assert.strictEqual(email.get('newsletter_id'), post.get('newsletter').id);
|
||||
assert.strictEqual(email.get('post_id'), post.id);
|
||||
assert.strictEqual(email.get('status'), 'pending');
|
||||
assert.strictEqual(email.get('source'), post.get('lexical'));
|
||||
assert.strictEqual(email.get('source_type'), 'lexical');
|
||||
});
|
||||
|
||||
it('Stores the error in the email model if scheduling fails', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
})
|
||||
});
|
||||
|
||||
scheduleEmail.throws(new Error('Test error'));
|
||||
|
||||
const email = await service.createEmail(post);
|
||||
sinon.assert.calledOnce(scheduleEmail);
|
||||
|
||||
assert.strictEqual(email.get('error'), 'Test error');
|
||||
assert.strictEqual(email.get('status'), 'failed');
|
||||
});
|
||||
|
||||
it('Stores a default error in the email model if scheduling fails', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
})
|
||||
});
|
||||
|
||||
scheduleEmail.throws(new Error());
|
||||
|
||||
const email = await service.createEmail(post);
|
||||
sinon.assert.calledOnce(scheduleEmail);
|
||||
|
||||
assert.strictEqual(email.get('error'), 'Something went wrong while scheduling the email');
|
||||
assert.strictEqual(email.get('status'), 'failed');
|
||||
});
|
||||
|
||||
it('Checks limits before scheduling', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
})
|
||||
});
|
||||
limited.emails = true;
|
||||
|
||||
await assert.rejects(service.createEmail(post));
|
||||
sinon.assert.notCalled(scheduleEmail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Retry email', function () {
|
||||
it('Schedules email again', async function () {
|
||||
const email = createModel({
|
||||
status: 'failed',
|
||||
error: 'Test error'
|
||||
});
|
||||
|
||||
await service.retryEmail(email);
|
||||
sinon.assert.calledOnce(scheduleEmail);
|
||||
});
|
||||
|
||||
it('Checks limits before scheduling', async function () {
|
||||
const email = createModel({
|
||||
status: 'failed',
|
||||
error: 'Test error'
|
||||
});
|
||||
|
||||
limited.emails = true;
|
||||
assert.rejects(service.retryEmail(email));
|
||||
sinon.assert.notCalled(scheduleEmail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExampleMember', function () {
|
||||
it('Returns a member', async function () {
|
||||
const member = createModel({
|
||||
uuid: '123',
|
||||
name: 'Example member',
|
||||
email: 'example@example.com'
|
||||
});
|
||||
membersRepository.get.resolves(member);
|
||||
const exampleMember = await service.getExampleMember('example@example.com');
|
||||
assert.strictEqual(exampleMember.id, member.id);
|
||||
assert.strictEqual(exampleMember.name, member.get('name'));
|
||||
assert.strictEqual(exampleMember.email, member.get('email'));
|
||||
assert.strictEqual(exampleMember.uuid, member.get('uuid'));
|
||||
});
|
||||
|
||||
it('Returns a member without name if member does not exist', async function () {
|
||||
membersRepository.get.resolves(undefined);
|
||||
const exampleMember = await service.getExampleMember('example@example.com');
|
||||
assert.strictEqual(exampleMember.name, '');
|
||||
assert.strictEqual(exampleMember.email, 'example@example.com');
|
||||
assert.ok(exampleMember.id);
|
||||
assert.ok(exampleMember.uuid);
|
||||
});
|
||||
|
||||
it('Returns a default member', async function () {
|
||||
membersRepository.get.resolves(undefined);
|
||||
const exampleMember = await service.getExampleMember();
|
||||
assert.ok(exampleMember.id);
|
||||
assert.ok(exampleMember.uuid);
|
||||
assert.ok(exampleMember.name);
|
||||
assert.ok(exampleMember.email);
|
||||
});
|
||||
});
|
||||
|
||||
describe('previewEmail', function () {
|
||||
it('Replaces replacements with example member', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
})
|
||||
});
|
||||
sinon.stub(emailRenderer, 'renderBody').resolves({
|
||||
html: 'Hello {name}, {name}',
|
||||
plaintext: 'Hello {name}',
|
||||
replacements: [
|
||||
{
|
||||
id: 'name',
|
||||
token: /{name}/g,
|
||||
getValue: (member) => {
|
||||
return member.name;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const data = await service.previewEmail(post, post.get('newsletter'), null);
|
||||
assert.strictEqual(data.html, 'Hello Jamie Larson, Jamie Larson');
|
||||
assert.strictEqual(data.plaintext, 'Hello Jamie Larson');
|
||||
assert.strictEqual(data.subject, 'Subject');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendTestEmail', function () {
|
||||
it('Sends a test email', async function () {
|
||||
const post = createModel({
|
||||
id: '123',
|
||||
newsletter: createModel({
|
||||
status: 'active',
|
||||
feedback_enabled: true
|
||||
})
|
||||
});
|
||||
await service.sendTestEmail(post, post.get('newsletter'), null, ['example@example.com']);
|
||||
sinon.assert.calledOnce(sendingService.send);
|
||||
const members = sendingService.send.firstCall.args[0].members;
|
||||
assert.strictEqual(members.length, 1);
|
||||
assert.strictEqual(members[0].email, 'example@example.com');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue