Added 100% test coverage for EmailEventStorage
refs https://github.com/TryGhost/Team/issues/2339
This commit is contained in:
parent
9ca2e3f183
commit
6a364e7779
@ -4,7 +4,7 @@ module.exports = class EmailBouncedEvent {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
id;
|
id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -25,7 +25,7 @@ module.exports = class EmailBouncedEvent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
* @type {{message: string, code: number, enhancedCode: string | null}}
|
* @type {{message: string, code: number, enhancedCode: string | null}|null}
|
||||||
*/
|
*/
|
||||||
error;
|
error;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ module.exports = class EmailTemporaryBouncedEvent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
* @type {{message: string, code: number, enhancedCode: string | null}}
|
* @type {{message: string, code: number, enhancedCode: string | null}|null}
|
||||||
*/
|
*/
|
||||||
error;
|
error;
|
||||||
|
|
||||||
|
@ -18,51 +18,27 @@ class EmailEventStorage {
|
|||||||
*/
|
*/
|
||||||
listen(domainEvents) {
|
listen(domainEvents) {
|
||||||
domainEvents.subscribe(EmailDeliveredEvent, async (event) => {
|
domainEvents.subscribe(EmailDeliveredEvent, async (event) => {
|
||||||
try {
|
await this.handleDelivered(event);
|
||||||
await this.handleDelivered(event);
|
|
||||||
} catch (err) {
|
|
||||||
logging.error(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
domainEvents.subscribe(EmailOpenedEvent, async (event) => {
|
domainEvents.subscribe(EmailOpenedEvent, async (event) => {
|
||||||
try {
|
await this.handleOpened(event);
|
||||||
await this.handleOpened(event);
|
|
||||||
} catch (err) {
|
|
||||||
logging.error(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
domainEvents.subscribe(EmailBouncedEvent, async (event) => {
|
domainEvents.subscribe(EmailBouncedEvent, async (event) => {
|
||||||
try {
|
await this.handlePermanentFailed(event);
|
||||||
await this.handlePermanentFailed(event);
|
|
||||||
} catch (e) {
|
|
||||||
logging.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
domainEvents.subscribe(EmailTemporaryBouncedEvent, async (event) => {
|
domainEvents.subscribe(EmailTemporaryBouncedEvent, async (event) => {
|
||||||
try {
|
await this.handleTemporaryFailed(event);
|
||||||
await this.handleTemporaryFailed(event);
|
|
||||||
} catch (e) {
|
|
||||||
logging.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
domainEvents.subscribe(EmailUnsubscribedEvent, async (event) => {
|
domainEvents.subscribe(EmailUnsubscribedEvent, async (event) => {
|
||||||
try {
|
await this.handleUnsubscribed(event);
|
||||||
await this.handleUnsubscribed(event);
|
|
||||||
} catch (e) {
|
|
||||||
logging.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
domainEvents.subscribe(SpamComplaintEvent, async (event) => {
|
domainEvents.subscribe(SpamComplaintEvent, async (event) => {
|
||||||
try {
|
await this.handleComplained(event);
|
||||||
await this.handleComplained(event);
|
|
||||||
} catch (e) {
|
|
||||||
logging.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,27 +2,20 @@ const EmailEventStorage = require('../lib/email-event-storage');
|
|||||||
const {EmailDeliveredEvent, EmailOpenedEvent, EmailBouncedEvent, EmailTemporaryBouncedEvent, EmailUnsubscribedEvent, SpamComplaintEvent} = require('@tryghost/email-events');
|
const {EmailDeliveredEvent, EmailOpenedEvent, EmailBouncedEvent, EmailTemporaryBouncedEvent, EmailUnsubscribedEvent, SpamComplaintEvent} = require('@tryghost/email-events');
|
||||||
const sinon = require('sinon');
|
const sinon = require('sinon');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
const logging = require('@tryghost/logging');
|
||||||
|
const {createDb} = require('./utils');
|
||||||
|
|
||||||
function stubDb() {
|
describe('Email Event Storage', function () {
|
||||||
const db = {
|
let logError;
|
||||||
knex: function () {
|
|
||||||
return this;
|
beforeEach(function () {
|
||||||
},
|
logError = sinon.stub(logging, 'error');
|
||||||
where: function () {
|
});
|
||||||
return this;
|
|
||||||
},
|
afterEach(function () {
|
||||||
whereNull: function () {
|
sinon.restore();
|
||||||
return this;
|
});
|
||||||
},
|
|
||||||
update: sinon.stub().resolves()
|
|
||||||
};
|
|
||||||
db.knex.raw = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Email event storage', function () {
|
|
||||||
describe('Constructor', function () {
|
describe('Constructor', function () {
|
||||||
it('doesn\'t throw', function () {
|
it('doesn\'t throw', function () {
|
||||||
new EmailEventStorage({});
|
new EmailEventStorage({});
|
||||||
@ -37,14 +30,15 @@ describe('Email event storage', function () {
|
|||||||
email: 'example@example.com',
|
email: 'example@example.com',
|
||||||
memberId: '123',
|
memberId: '123',
|
||||||
emailId: '456',
|
emailId: '456',
|
||||||
emailRecipientId: '789'
|
emailRecipientId: '789',
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
const db = stubDb();
|
const db = createDb();
|
||||||
const eventHandler = new EmailEventStorage({db});
|
const eventHandler = new EmailEventStorage({db});
|
||||||
eventHandler.listen(DomainEvents);
|
eventHandler.listen(DomainEvents);
|
||||||
sinon.assert.callCount(subscribeSpy, 6);
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
@ -60,14 +54,15 @@ describe('Email event storage', function () {
|
|||||||
email: 'example@example.com',
|
email: 'example@example.com',
|
||||||
memberId: '123',
|
memberId: '123',
|
||||||
emailId: '456',
|
emailId: '456',
|
||||||
emailRecipientId: '789'
|
emailRecipientId: '789',
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
const db = stubDb();
|
const db = createDb();
|
||||||
const eventHandler = new EmailEventStorage({db});
|
const eventHandler = new EmailEventStorage({db});
|
||||||
eventHandler.listen(DomainEvents);
|
eventHandler.listen(DomainEvents);
|
||||||
sinon.assert.callCount(subscribeSpy, 6);
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
@ -90,14 +85,15 @@ describe('Email event storage', function () {
|
|||||||
message: 'test',
|
message: 'test',
|
||||||
code: 500,
|
code: 500,
|
||||||
enhancedCode: '5.5.5'
|
enhancedCode: '5.5.5'
|
||||||
}
|
},
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
const db = stubDb();
|
const db = createDb();
|
||||||
const existing = {
|
const existing = {
|
||||||
id: 1,
|
id: 1,
|
||||||
get: (key) => {
|
get: (key) => {
|
||||||
@ -146,14 +142,15 @@ describe('Email event storage', function () {
|
|||||||
message: 'test',
|
message: 'test',
|
||||||
code: 500,
|
code: 500,
|
||||||
enhancedCode: '5.5.5'
|
enhancedCode: '5.5.5'
|
||||||
}
|
},
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
const db = stubDb();
|
const db = createDb();
|
||||||
const EmailRecipientFailure = {
|
const EmailRecipientFailure = {
|
||||||
transaction: async function (callback) {
|
transaction: async function (callback) {
|
||||||
return await callback(1);
|
return await callback(1);
|
||||||
@ -176,6 +173,37 @@ describe('Email event storage', function () {
|
|||||||
assert(EmailRecipientFailure.add.calledOnce);
|
assert(EmailRecipientFailure.add.calledOnce);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Handles email permanent bounce event without error data', async function () {
|
||||||
|
let waitPromise;
|
||||||
|
|
||||||
|
const DomainEvents = {
|
||||||
|
subscribe: async (type, handler) => {
|
||||||
|
if (type === EmailBouncedEvent) {
|
||||||
|
waitPromise = handler(EmailBouncedEvent.create({
|
||||||
|
email: 'example@example.com',
|
||||||
|
memberId: '123',
|
||||||
|
emailId: '456',
|
||||||
|
emailRecipientId: '789',
|
||||||
|
error: null,
|
||||||
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
|
const db = createDb();
|
||||||
|
|
||||||
|
const eventHandler = new EmailEventStorage({
|
||||||
|
db,
|
||||||
|
models: {}
|
||||||
|
});
|
||||||
|
eventHandler.listen(DomainEvents);
|
||||||
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
|
await waitPromise;
|
||||||
|
sinon.assert.calledOnce(db.update);
|
||||||
|
});
|
||||||
|
|
||||||
it('Handles email permanent bounce events with skipped update', async function () {
|
it('Handles email permanent bounce events with skipped update', async function () {
|
||||||
let waitPromise;
|
let waitPromise;
|
||||||
|
|
||||||
@ -191,14 +219,15 @@ describe('Email event storage', function () {
|
|||||||
message: 'test',
|
message: 'test',
|
||||||
code: 500,
|
code: 500,
|
||||||
enhancedCode: '5.5.5'
|
enhancedCode: '5.5.5'
|
||||||
}
|
},
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
const db = stubDb();
|
const db = createDb();
|
||||||
const existing = {
|
const existing = {
|
||||||
id: 1,
|
id: 1,
|
||||||
get: (key) => {
|
get: (key) => {
|
||||||
@ -247,9 +276,10 @@ describe('Email event storage', function () {
|
|||||||
error: {
|
error: {
|
||||||
message: 'test',
|
message: 'test',
|
||||||
code: 500,
|
code: 500,
|
||||||
enhancedCode: '5.5.5'
|
enhancedCode: null
|
||||||
}
|
},
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -285,6 +315,59 @@ describe('Email event storage', function () {
|
|||||||
assert(existing.save.calledOnce);
|
assert(existing.save.calledOnce);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Handles email temporary bounce events with skipped update', async function () {
|
||||||
|
let waitPromise;
|
||||||
|
|
||||||
|
const DomainEvents = {
|
||||||
|
subscribe: async (type, handler) => {
|
||||||
|
if (type === EmailTemporaryBouncedEvent) {
|
||||||
|
waitPromise = handler(EmailTemporaryBouncedEvent.create({
|
||||||
|
email: 'example@example.com',
|
||||||
|
memberId: '123',
|
||||||
|
emailId: '456',
|
||||||
|
emailRecipientId: '789',
|
||||||
|
error: {
|
||||||
|
message: 'test',
|
||||||
|
code: 500,
|
||||||
|
enhancedCode: '5.5.5'
|
||||||
|
},
|
||||||
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
|
const existing = {
|
||||||
|
id: 1,
|
||||||
|
get: (key) => {
|
||||||
|
if (key === 'severity') {
|
||||||
|
return 'temporary';
|
||||||
|
}
|
||||||
|
if (key === 'failed_at') {
|
||||||
|
return new Date(5);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save: sinon.stub().resolves()
|
||||||
|
};
|
||||||
|
const EmailRecipientFailure = {
|
||||||
|
transaction: async function (callback) {
|
||||||
|
return await callback(1);
|
||||||
|
},
|
||||||
|
findOne: sinon.stub().resolves(existing)
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventHandler = new EmailEventStorage({
|
||||||
|
models: {
|
||||||
|
EmailRecipientFailure
|
||||||
|
}
|
||||||
|
});
|
||||||
|
eventHandler.listen(DomainEvents);
|
||||||
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
|
await waitPromise;
|
||||||
|
assert(existing.save.notCalled);
|
||||||
|
});
|
||||||
|
|
||||||
it('Handles unsubscribe', async function () {
|
it('Handles unsubscribe', async function () {
|
||||||
let waitPromise;
|
let waitPromise;
|
||||||
|
|
||||||
@ -294,8 +377,9 @@ describe('Email event storage', function () {
|
|||||||
waitPromise = handler(EmailUnsubscribedEvent.create({
|
waitPromise = handler(EmailUnsubscribedEvent.create({
|
||||||
email: 'example@example.com',
|
email: 'example@example.com',
|
||||||
memberId: '123',
|
memberId: '123',
|
||||||
emailId: '456'
|
emailId: '456',
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -324,8 +408,9 @@ describe('Email event storage', function () {
|
|||||||
waitPromise = handler(SpamComplaintEvent.create({
|
waitPromise = handler(SpamComplaintEvent.create({
|
||||||
email: 'example@example.com',
|
email: 'example@example.com',
|
||||||
memberId: '123',
|
memberId: '123',
|
||||||
emailId: '456'
|
emailId: '456',
|
||||||
}, new Date(0)));
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -345,4 +430,70 @@ describe('Email event storage', function () {
|
|||||||
await waitPromise;
|
await waitPromise;
|
||||||
assert(EmailSpamComplaintEvent.add.calledOnce);
|
assert(EmailSpamComplaintEvent.add.calledOnce);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Handles duplicate complaints', async function () {
|
||||||
|
let waitPromise;
|
||||||
|
|
||||||
|
const DomainEvents = {
|
||||||
|
subscribe: async (type, handler) => {
|
||||||
|
if (type === SpamComplaintEvent) {
|
||||||
|
waitPromise = handler(SpamComplaintEvent.create({
|
||||||
|
email: 'example@example.com',
|
||||||
|
memberId: '123',
|
||||||
|
emailId: '456',
|
||||||
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
|
const EmailSpamComplaintEvent = {
|
||||||
|
add: sinon.stub().rejects({code: 'ER_DUP_ENTRY'})
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventHandler = new EmailEventStorage({
|
||||||
|
models: {
|
||||||
|
EmailSpamComplaintEvent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
eventHandler.listen(DomainEvents);
|
||||||
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
|
await waitPromise;
|
||||||
|
assert(EmailSpamComplaintEvent.add.calledOnce);
|
||||||
|
assert(!logError.calledOnce);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles logging failed complaint storage', async function () {
|
||||||
|
let waitPromise;
|
||||||
|
|
||||||
|
const DomainEvents = {
|
||||||
|
subscribe: async (type, handler) => {
|
||||||
|
if (type === SpamComplaintEvent) {
|
||||||
|
waitPromise = handler(SpamComplaintEvent.create({
|
||||||
|
email: 'example@example.com',
|
||||||
|
memberId: '123',
|
||||||
|
emailId: '456',
|
||||||
|
timestamp: new Date(0)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeSpy = sinon.spy(DomainEvents, 'subscribe');
|
||||||
|
const EmailSpamComplaintEvent = {
|
||||||
|
add: sinon.stub().rejects(new Error('Some database error'))
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventHandler = new EmailEventStorage({
|
||||||
|
models: {
|
||||||
|
EmailSpamComplaintEvent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
eventHandler.listen(DomainEvents);
|
||||||
|
sinon.assert.callCount(subscribeSpy, 6);
|
||||||
|
await waitPromise;
|
||||||
|
assert(EmailSpamComplaintEvent.add.calledOnce);
|
||||||
|
assert(logError.calledOnce);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -59,7 +59,7 @@ const createModelClass = (options = {}) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDb = ({first, all}) => {
|
const createDb = ({first, all} = {}) => {
|
||||||
let a = all;
|
let a = all;
|
||||||
const db = {
|
const db = {
|
||||||
knex: function () {
|
knex: function () {
|
||||||
|
Loading…
Reference in New Issue
Block a user