mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 05:14:12 +03:00
eeb7546abb
refs https://www.notion.so/ghost/Marketing-Milestone-email-campaigns-1d2c9dee3cfa4029863edb16092ad5c4?pvs=4 - When milestones will be activated we would send out emails to users that are way above the achieved milestone, as we didn't record milestones before - The plan is to implement a 0 milestone and don't send an email for achieving those and also add all achieved milestones in the first run until a first milestone is stored in the DB, then increment from there. - This change takes care of two cases: 1. Milestones gets enabled and runs initially. We don't want to send emails unless there's already at least one milestone achieved. For that we add a 0 milestone helper and add a `initial` reason to the meta object for the milestone event, so we can choose not to ping Slack and also disable email sending for all milestones achieved in this initial run. 2. All achieved milestones will be stored in the DB, even when that means we skip some. This introduces the `skipped` reason which also doesn't send emails for the skipped milestones, but will do for correctly achieved milestones (always the highest one). - Added handling for slack notifications to not attempt sending when reason is `skipped` or `initial`
603 lines
25 KiB
JavaScript
603 lines
25 KiB
JavaScript
const assert = require('assert');
|
|
const {
|
|
MilestonesService,
|
|
InMemoryMilestoneRepository
|
|
} = require('../index');
|
|
const Milestone = require('../lib/Milestone');
|
|
const DomainEvents = require('@tryghost/domain-events');
|
|
const sinon = require('sinon');
|
|
|
|
describe('MilestonesService', function () {
|
|
let repository;
|
|
let domainEventSpy;
|
|
|
|
beforeEach(async function () {
|
|
domainEventSpy = sinon.spy(DomainEvents, 'dispatch');
|
|
});
|
|
|
|
afterEach(function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
const milestonesConfig = {
|
|
arr: [
|
|
{
|
|
currency: 'usd',
|
|
values: [0, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
|
|
},
|
|
{
|
|
currency: 'gbp',
|
|
values: [0, 500, 1000, 5000, 100000, 250000, 500000, 1000000]
|
|
},
|
|
{
|
|
currency: 'idr',
|
|
values: [0, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
|
|
},
|
|
{
|
|
currency: 'eur',
|
|
values: [0, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
|
|
}
|
|
],
|
|
members: [0, 100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000],
|
|
minDaysSinceImported: 7,
|
|
minDaysSinceLastEmail: 14
|
|
};
|
|
|
|
describe('ARR Milestones', function () {
|
|
it('Adds initial 0 ARR milestone without sending email', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'usd', arr: 43}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'usd');
|
|
assert(arrResult.value === 0);
|
|
assert(arrResult.emailSentAt === null);
|
|
assert(arrResult.name === 'arr-0-usd');
|
|
|
|
const domainEventSpyResult = domainEventSpy.getCall(0).args[0];
|
|
assert(domainEventSpy.calledOnce === true);
|
|
assert(domainEventSpyResult.data.milestone);
|
|
assert(domainEventSpyResult.data.meta.currentValue === 43);
|
|
assert(domainEventSpyResult.data.meta.reason === 'initial');
|
|
});
|
|
|
|
it('Adds first ARR milestones but does not send email if no previous milestones', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'usd', arr: 1298}, {currency: 'nzd', arr: 600}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'usd');
|
|
assert(arrResult.value === 1000);
|
|
assert(arrResult.emailSentAt === null);
|
|
assert(arrResult.name === 'arr-1000-usd');
|
|
|
|
assert(domainEventSpy.calledTwice === true);
|
|
const firstDomainEventSpyCall = domainEventSpy.getCall(0).args[0];
|
|
const secondDomainEventSpyCall = domainEventSpy.getCall(1).args[0];
|
|
assert(firstDomainEventSpyCall.data.milestone);
|
|
assert(firstDomainEventSpyCall.data.meta.currentValue === 1298);
|
|
assert(firstDomainEventSpyCall.data.meta.reason === 'initial');
|
|
assert(secondDomainEventSpyCall.data.milestone);
|
|
assert(secondDomainEventSpyCall.data.meta.currentValue === 1298);
|
|
assert(secondDomainEventSpyCall.data.meta.reason === 'initial');
|
|
});
|
|
|
|
it('Adds next ARR milestone and sends email', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneOne = await Milestone.create({
|
|
type: 'arr',
|
|
value: 100,
|
|
createdAt: '2023-01-01T00:00:00Z',
|
|
emailSentAt: '2023-01-01T00:00:00Z'
|
|
});
|
|
|
|
const milestoneTwo = await Milestone.create({
|
|
type: 'arr',
|
|
value: 500,
|
|
createdAt: '2023-01-02T00:00:00Z',
|
|
emailSentAt: '2023-01-02T00:00:00Z'
|
|
});
|
|
|
|
const milestoneThree = await Milestone.create({
|
|
type: 'arr',
|
|
value: 1000,
|
|
currency: 'eur',
|
|
createdAt: '2023-01-15T00:00:00Z',
|
|
emailSentAt: '2023-01-15T00:00:00Z'
|
|
});
|
|
|
|
await repository.save(milestoneOne);
|
|
await repository.save(milestoneTwo);
|
|
await repository.save(milestoneThree);
|
|
|
|
assert(domainEventSpy.callCount === 3);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
// Same ARR values for both supported currencies
|
|
return [{currency: 'usd', arr: 10001}, {currency: 'eur', arr: 10001}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'usd');
|
|
assert(arrResult.value === 10000);
|
|
assert(arrResult.emailSentAt !== null);
|
|
assert(arrResult.name === 'arr-10000-usd');
|
|
assert(domainEventSpy.callCount === 6); // we have just created three new milestones, but we only sent the email for the last one
|
|
const firstDomainEventSpyResult = domainEventSpy.getCall(3).args[0];
|
|
assert(firstDomainEventSpyResult.data.milestone);
|
|
assert(firstDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const secondDomainEventSpyResult = domainEventSpy.getCall(4).args[0];
|
|
assert(secondDomainEventSpyResult.data.milestone);
|
|
assert(secondDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const thirdDomainEventSpyResult = domainEventSpy.getCall(5).args[0];
|
|
assert(thirdDomainEventSpyResult.data.milestone);
|
|
assert(thirdDomainEventSpyResult.data.meta.currentValue === 10001);
|
|
assert(thirdDomainEventSpyResult.data.meta.reason === undefined);
|
|
});
|
|
|
|
it('Does not add ARR milestone for out of scope currency', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'nzd', arr: 1005}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'nzd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult === undefined);
|
|
assert(domainEventSpy.callCount === 0);
|
|
});
|
|
|
|
it('Does not add new ARR milestone if already achieved', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'arr',
|
|
value: 5000,
|
|
currency: 'gbp',
|
|
emailSentAt: '2023-01-01T00:00:00Z'
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'gbp', arr: 5005}, {currency: 'usd', arr: 100}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'gbp';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'gbp');
|
|
assert(arrResult.value === 5000);
|
|
assert(arrResult.name === 'arr-5000-gbp');
|
|
assert(domainEventSpy.callCount === 4);
|
|
// Filled up missing milestones, but only if they don't exist already
|
|
const firstDomainEventSpyResult = domainEventSpy.getCall(1).args[0];
|
|
assert(firstDomainEventSpyResult.data.milestone);
|
|
assert(firstDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const secondDomainEventSpyResult = domainEventSpy.getCall(2).args[0];
|
|
assert(secondDomainEventSpyResult.data.milestone);
|
|
assert(secondDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const thirdDomainEventSpyResult = domainEventSpy.getCall(3).args[0];
|
|
assert(thirdDomainEventSpyResult.data.milestone);
|
|
assert(thirdDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
assert(thirdDomainEventSpyResult.data.meta.currentValue === 5005);
|
|
});
|
|
|
|
it('Adds ARR milestone but does not send email if imported members are detected', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'arr',
|
|
value: 0,
|
|
currency: 'usd',
|
|
emailSentAt: '2023-01-01T00:00:00Z'
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'usd', arr: 100000}, {currency: 'idr', arr: 2600}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return true;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'usd');
|
|
assert(arrResult.value === 100000);
|
|
assert(arrResult.emailSentAt === null);
|
|
assert(domainEventSpy.callCount === 5);
|
|
const secondDomainEventSpyResult = domainEventSpy.getCall(1).args[0];
|
|
assert(secondDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const lastDomainEventSpyResult = domainEventSpy.getCall(4).args[0];
|
|
assert(lastDomainEventSpyResult.data.meta.reason === 'import');
|
|
});
|
|
|
|
it('Adds ARR milestone but does not send email if last email was too recent', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const lessThanTwoWeeksAgo = new Date();
|
|
lessThanTwoWeeksAgo.setDate(lessThanTwoWeeksAgo.getDate() - 12);
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'arr',
|
|
value: 1000,
|
|
currency: 'idr',
|
|
emailSentAt: lessThanTwoWeeksAgo
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getARR() {
|
|
return [{currency: 'idr', arr: 10000}];
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'idr';
|
|
}
|
|
}
|
|
});
|
|
|
|
const arrResult = await milestoneEmailService.checkMilestones('arr');
|
|
assert(arrResult.type === 'arr');
|
|
assert(arrResult.currency === 'idr');
|
|
assert(arrResult.value === 10000);
|
|
assert(arrResult.emailSentAt === null);
|
|
assert(domainEventSpy.callCount === 3); // two new milestones created
|
|
const lastDomainEventSpyResult = domainEventSpy.getCall(2).args[0];
|
|
assert(lastDomainEventSpyResult.data.meta.reason === 'email');
|
|
});
|
|
});
|
|
|
|
describe('Members Milestones', function () {
|
|
it('Adds initial 0 Members milestone without sending email', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 6;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.value === 0);
|
|
assert(membersResult.emailSentAt === null);
|
|
assert(membersResult.name === 'members-0');
|
|
|
|
const domainEventSpyResult = domainEventSpy.getCall(0).args[0];
|
|
assert(domainEventSpy.calledOnce === true);
|
|
assert(domainEventSpyResult.data.milestone);
|
|
assert(domainEventSpyResult.data.meta.currentValue === 6);
|
|
assert(domainEventSpyResult.data.meta.reason === 'initial');
|
|
});
|
|
|
|
it('Adds first Members milestone but does not send email if no previous milestones', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 110;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.value === 100);
|
|
assert(membersResult.emailSentAt === null);
|
|
assert(domainEventSpy.callCount === 2);
|
|
|
|
assert(domainEventSpy.calledTwice === true);
|
|
const firstDomainEventSpyCall = domainEventSpy.getCall(0).args[0];
|
|
const secondDomainEventSpyCall = domainEventSpy.getCall(1).args[0];
|
|
assert(firstDomainEventSpyCall.data.milestone);
|
|
assert(firstDomainEventSpyCall.data.meta.currentValue === 110);
|
|
assert(firstDomainEventSpyCall.data.meta.reason === 'initial');
|
|
assert(secondDomainEventSpyCall.data.milestone);
|
|
assert(secondDomainEventSpyCall.data.meta.currentValue === 110);
|
|
assert(secondDomainEventSpyCall.data.meta.reason === 'initial');
|
|
});
|
|
|
|
it('Adds next Members milestone and sends email', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestoneOne = await Milestone.create({
|
|
type: 'members',
|
|
value: 1000,
|
|
createdAt: '2023-01-01T00:00:00Z',
|
|
emailSentAt: '2023-01-01T00:00:00Z'
|
|
});
|
|
|
|
const milestoneTwo = await Milestone.create({
|
|
type: 'members',
|
|
value: 500,
|
|
createdAt: '2023-01-02T00:00:00Z',
|
|
emailSentAt: '2023-01-02T00:00:00Z'
|
|
});
|
|
|
|
const milestoneThree = await Milestone.create({
|
|
type: 'members',
|
|
value: 1000,
|
|
createdAt: '2023-01-15T00:00:00Z',
|
|
emailSentAt: '2023-01-15T00:00:00Z'
|
|
});
|
|
|
|
await repository.save(milestoneOne);
|
|
await repository.save(milestoneTwo);
|
|
await repository.save(milestoneThree);
|
|
|
|
assert(domainEventSpy.callCount === 3);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 50005;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.currency === null);
|
|
assert(membersResult.value === 50000);
|
|
assert(membersResult.emailSentAt !== null);
|
|
assert(membersResult.name === 'members-50000');
|
|
|
|
assert(domainEventSpy.callCount === 7); // we have just created three new milestones, but we only sent the email for the last one
|
|
const firstDomainEventSpyResult = domainEventSpy.getCall(3).args[0];
|
|
assert(firstDomainEventSpyResult.data.milestone);
|
|
assert(firstDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const secondDomainEventSpyResult = domainEventSpy.getCall(4).args[0];
|
|
assert(secondDomainEventSpyResult.data.milestone);
|
|
assert(secondDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const thirdDomainEventSpyResult = domainEventSpy.getCall(5).args[0];
|
|
assert(thirdDomainEventSpyResult.data.milestone);
|
|
assert(thirdDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const fourthDomainEventSpyResult = domainEventSpy.getCall(6).args[0];
|
|
assert(fourthDomainEventSpyResult.data.milestone);
|
|
assert(fourthDomainEventSpyResult.data.meta.currentValue === 50005);
|
|
assert(fourthDomainEventSpyResult.data.meta.reason === undefined);
|
|
});
|
|
|
|
it('Does not add new Members milestone if already achieved', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'members',
|
|
value: 50000
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 50555;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.value === 50000);
|
|
assert(membersResult.name === 'members-50000');
|
|
assert(domainEventSpy.callCount === 5);
|
|
// Filled up missing milestones, but only if they don't exist already
|
|
const firstDomainEventSpyResult = domainEventSpy.getCall(1).args[0];
|
|
assert(firstDomainEventSpyResult.data.milestone);
|
|
assert(firstDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const secondDomainEventSpyResult = domainEventSpy.getCall(2).args[0];
|
|
assert(secondDomainEventSpyResult.data.milestone);
|
|
assert(secondDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
const thirdDomainEventSpyResult = domainEventSpy.getCall(3).args[0];
|
|
assert(thirdDomainEventSpyResult.data.milestone);
|
|
assert(thirdDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
assert(thirdDomainEventSpyResult.data.meta.currentValue === 50555);
|
|
assert(thirdDomainEventSpyResult.data.meta.reason === 'skipped');
|
|
});
|
|
|
|
it('Adds Members milestone but does not send email if imported members are detected', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'members',
|
|
value: 100
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 1001;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return true;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.value === 1000);
|
|
assert(membersResult.emailSentAt === null);
|
|
assert(domainEventSpy.callCount === 3);
|
|
const lastDomainEventSpyResult = domainEventSpy.getCall(2).args[0];
|
|
assert(lastDomainEventSpyResult.data.meta.reason === 'import');
|
|
});
|
|
|
|
it('Adds Members milestone but does not send email if last email was too recent', async function () {
|
|
repository = new InMemoryMilestoneRepository({DomainEvents});
|
|
|
|
const lessThanTwoWeeksAgo = new Date();
|
|
lessThanTwoWeeksAgo.setDate(lessThanTwoWeeksAgo.getDate() - 8);
|
|
|
|
const milestone = await Milestone.create({
|
|
type: 'members',
|
|
value: 100,
|
|
emailSentAt: lessThanTwoWeeksAgo
|
|
});
|
|
|
|
await repository.save(milestone);
|
|
|
|
assert(domainEventSpy.callCount === 1);
|
|
|
|
const milestoneEmailService = new MilestonesService({
|
|
repository,
|
|
milestonesConfig,
|
|
queries: {
|
|
async getMembersCount() {
|
|
return 50010;
|
|
},
|
|
async hasImportedMembersInPeriod() {
|
|
return false;
|
|
},
|
|
async getDefaultCurrency() {
|
|
return 'usd';
|
|
}
|
|
}
|
|
});
|
|
|
|
const membersResult = await milestoneEmailService.checkMilestones('members');
|
|
assert(membersResult.type === 'members');
|
|
assert(membersResult.value === 50000);
|
|
assert(membersResult.emailSentAt === null);
|
|
assert(domainEventSpy.callCount === 5);
|
|
const lastDomainEventSpyResult = domainEventSpy.getCall(4).args[0];
|
|
assert(lastDomainEventSpyResult.data.meta.reason === 'email');
|
|
});
|
|
});
|
|
});
|