Ghost/ghost/slack-notifications/test/SlackNotificationsService.test.js
Aileen Booker 2f57e95a5d
Slack notifications service for Milestones behind flag (#16281)
refs
https://www.notion.so/ghost/Marketing-Milestone-email-campaigns-1d2c9dee3cfa4029863edb16092ad5c4?pvs=4

- Added a `slack-notifications` repository which handles sending Slack
messages to a URL as defined in our Ghost(Pro) config (also includes a
global switch to disable the feature if needed) and listens to
`MilestoneCreatedEvents`.
- Added a `slack-notification` service which listens to the events on
boot.
- In order to have access to further information such as the reason why
a Milestone email hasn't been sent, or the current ARR or Member value
as comparison to the achieved milestone, I added a `meta` object to the
`MilestoneCreatedEvent` which then gets accessible by the event
subscriber. This avoid doing further requests to the DB as we need to
have this information in relation to the event occurred.

---------

Co-authored-by: Fabien "egg" O'Carroll <fabien@allou.is>
2023-02-17 12:59:18 +02:00

183 lines
6.2 KiB
JavaScript

const assert = require('assert');
const sinon = require('sinon');
const {SlackNotificationsService} = require('../index');
const ObjectId = require('bson-objectid').default;
const {MilestoneCreatedEvent} = require('@tryghost/milestones');
const DomainEvents = require('@tryghost/domain-events');
describe('SlackNotificationsService', function () {
describe('Constructor', function () {
it('doesn\'t throw', function () {
new SlackNotificationsService({});
});
});
describe('Slack notifications service', function () {
let service;
let slackNotificationStub;
let loggingSpy;
const config = {
isEnabled: true,
webhookUrl: 'https://slack-webhook.example'
};
beforeEach(function () {
slackNotificationStub = sinon.stub().resolves();
loggingSpy = sinon.spy();
});
afterEach(function () {
sinon.restore();
});
describe('subscribeEvents', function () {
it('subscribes to events', async function () {
const subscribeStub = sinon.stub().resolves();
service = new SlackNotificationsService({
logging: {
warn: () => {},
error: loggingSpy
},
DomainEvents: {
subscribe: subscribeStub
},
siteUrl: 'https://ghost.example',
config,
slackNotifications: {
notifyMilestoneReceived: slackNotificationStub
}
});
service.subscribeEvents();
assert(subscribeStub.callCount === 1);
assert(subscribeStub.calledWith(MilestoneCreatedEvent) === true);
});
it('handles milestone created event', async function () {
service = new SlackNotificationsService({
logging: {
warn: () => {},
error: loggingSpy
},
DomainEvents,
siteUrl: 'https://ghost.example',
config,
slackNotifications: {
notifyMilestoneReceived: slackNotificationStub
}
});
service.subscribeEvents();
DomainEvents.dispatch(MilestoneCreatedEvent.create({
milestone: {
id: new ObjectId().toHexString(),
type: 'arr',
value: 1000,
currency: 'usd',
createdAt: new Date(),
emailSentAt: new Date()
},
meta: {
currentARR: 1398
}
}));
await DomainEvents.allSettled();
assert(loggingSpy.callCount === 0);
assert(slackNotificationStub.calledOnce);
});
it('does not send notification when milestones is disabled in hostSettings', async function () {
service = new SlackNotificationsService({
logging: {
warn: () => {},
error: loggingSpy
},
DomainEvents,
siteUrl: 'https://ghost.example',
config: {
isEnabled: false,
webhookUrl: 'https://slack-webhook.example'
},
slackNotifications: {
notifyMilestoneReceived: slackNotificationStub
}
});
service.subscribeEvents();
DomainEvents.dispatch(MilestoneCreatedEvent.create({milestone: {}}));
await DomainEvents.allSettled();
assert(loggingSpy.callCount === 0);
assert(slackNotificationStub.callCount === 0);
});
it('does not send notification when no url in hostSettings provided', async function () {
service = new SlackNotificationsService({
logging: {
warn: () => {},
error: loggingSpy
},
DomainEvents,
siteUrl: 'https://ghost.example',
config: {
isEnabled: true,
webhookUrl: null
},
slackNotifications: {
notifyMilestoneReceived: slackNotificationStub
}
});
service.subscribeEvents();
DomainEvents.dispatch(MilestoneCreatedEvent.create({milestone: {}}));
await DomainEvents.allSettled();
assert(loggingSpy.callCount === 0);
assert(slackNotificationStub.callCount === 0);
});
it('logs error when event handling fails', async function () {
service = new SlackNotificationsService({
logging: {
warn: () => {},
error: loggingSpy
},
DomainEvents,
siteUrl: 'https://ghost.example',
config,
slackNotifications: {
async notifyMilestoneReceived() {
throw new Error('test');
}
}
});
service.subscribeEvents();
DomainEvents.dispatch(MilestoneCreatedEvent.create({
milestone: {
type: 'members',
name: 'members-100',
value: 100,
createdAt: new Date()
}
}));
await DomainEvents.allSettled();
const loggingSpyCall = loggingSpy.getCall(0).args[0];
assert(loggingSpy.calledOnce);
assert(loggingSpyCall instanceof Error);
});
});
});
});