Ghost/ghost/core/test/e2e-server/services/milestones.test.js
Daniel Lockyer 89493893d1 Removed all unused variables from test files
- this cleans up all imports or variables that aren't currently being used
- this really helps keep the tests clean by only allowing what is needed
- I've left `should` as an exemption for now because we need to clean up
  how it is used
2023-03-10 14:29:55 +01:00

311 lines
11 KiB
JavaScript

const {agentProvider, fixtureManager, mockManager, configUtils} = require('../../utils/e2e-framework');
const assert = require('assert');
const nock = require('nock');
const sinon = require('sinon');
const models = require('../../../core/server/models');
const moment = require('moment');
const logging = require('@tryghost/logging');
const milestonesService = require('../../../core/server/services/milestones');
let agent;
let counter = 0;
let membersCounter = 0;
const fifteenDays = 1000 * 60 * 60 * 24 * 15;
async function createMemberWithSubscription(interval, amount, currency, date) {
counter += 1;
membersCounter += 1;
const fakePrice = {
id: 'price_' + counter,
product: '',
active: true,
nickname: 'Paid',
unit_amount: amount,
currency,
type: 'recurring',
recurring: {
interval
}
};
const fakeSubscription = {
id: 'sub_' + counter,
customer: 'cus_' + counter,
status: 'active',
cancel_at_period_end: false,
metadata: {},
current_period_end: Math.floor(Date.now() / 1000 + 1000),
start_date: moment(date).unix(),
plan: fakePrice,
items: {
data: [{
price: fakePrice
}]
}
};
const fakeCustomer = {
id: 'cus_' + counter,
name: 'Test Member',
email: 'create-member-subscription-' + counter + '@email.com',
subscriptions: {
type: 'list',
data: [fakeSubscription]
}
};
nock('https://api.stripe.com')
.persist()
.get(/v1\/.*/)
.reply((uri) => {
const [match, resource] = uri.match(/\/?v1\/(\w+)\/?(\w+)/) || [null];
if (!match) {
return [500];
}
if (resource === 'customers') {
return [200, fakeCustomer];
}
if (resource === 'subscriptions') {
return [200, fakeSubscription];
}
});
const initialMember = {
name: fakeCustomer.name,
email: fakeCustomer.email,
subscribed: true,
stripe_customer_id: fakeCustomer.id
};
await agent
.post(`/members/`)
.body({members: [initialMember]})
.expectStatus(201);
nock.cleanAll();
}
async function createFreeMembers(amount, amountImported = 0) {
const members = [];
const newsletters = await agent.get(`/newsletters/`);
const newsletter = newsletters.body?.newsletters?.[0];
for (let index = 0; index < amount; index++) {
let membersAddRequest;
membersCounter += 1;
const member = {
name: 'Test Member',
email: 'free-member-' + membersCounter + '@email.com',
status: 'free',
uuid: `f6f91461-d7d8-4a3f-aa5d-8e582c40b99${membersCounter}`
};
if (amountImported > 0) {
member.subscribed = true;
member.newsletters = [newsletter];
const createMemberEvent = await agent
.post(`/members/`)
.body({members: [member]})
.expectStatus(201);
const id = createMemberEvent.body.members[0].id;
// Manually add the members_subscribe_event so we can test imported members
const editedEvent = await models.MemberSubscribeEvent.add({
newsletter_id: newsletter.id,
member_id: id,
subscribed: true,
source: index < amountImported ? 'import' : 'member'
});
membersAddRequest = Promise.all([createMemberEvent, editedEvent]);
} else {
membersAddRequest = await agent
.post(`/members/`)
.body({members: [member]})
.expectStatus(201);
}
members.push(membersAddRequest);
}
await Promise.all(members);
}
describe('Milestones Service', function () {
let clock;
let loggingStub;
const milestonesConfig = {
arr: [{currency: 'usd', values: [100, 150]}],
members: [10, 20, 30],
minDaysSinceImported: 7,
minDaysSinceLastEmail: 14,
maxPercentageFromMilestone: 0.35
};
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('newsletters');
await agent.loginAsOwner();
});
beforeEach(async function () {
loggingStub = sinon.stub(logging, 'info');
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
clock = sinon.useFakeTimers({
now: threeMonthsAgo.getTime(),
toFake: ['setTimeout']
});
sinon.createSandbox();
configUtils.set('milestones', milestonesConfig);
mockManager.mockLabsEnabled('milestoneEmails');
mockManager.mockMail();
});
afterEach(async function () {
await configUtils.restore();
mockManager.restore();
clock.restore();
clock = undefined;
sinon.restore();
});
it('Inits milestone service', async function () {
await milestonesService.init();
assert.ok(milestonesService.api);
});
it('Runs ARR and Members milestone jobs', async function () {
mockManager.mockSetting('stripe_connect_publishable_key', 'pk_live_89843uihsidfh98832uo8ri');
// No ARR and no members
const firstResultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const firstRun = await firstResultPromise;
assert(firstRun.members === undefined);
assert(firstRun.arr === undefined);
await createFreeMembers(7);
await createMemberWithSubscription('year', 5000, 'usd', '2000-01-10');
await createMemberWithSubscription('month', 100, 'usd', '2000-01-10');
const secondResultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const secondRun = await secondResultPromise;
assert(secondRun.members === undefined);
assert(secondRun.arr === undefined);
// Reached the first milestone for members
await createFreeMembers(1);
const thirdResultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const thirdRun = await thirdResultPromise;
assert(thirdRun.members.value === 10);
assert(thirdRun.members.emailSentAt !== undefined);
assert(thirdRun.arr === undefined);
const memberMilestoneModel = await models.Milestone.findOne({value: 10, type: 'members'});
assert.ok(memberMilestoneModel.get('id'));
assert.equal(memberMilestoneModel.get('type'), 'members');
assert.equal(memberMilestoneModel.get('value'), 10);
assert.ok(memberMilestoneModel.get('created_at'));
assert.ok(memberMilestoneModel.get('email_sent_at'));
// Reached the first milestone for ARR
// - Members already reached, no new one will be created
// - Less than two weeks since last milestone email, no email will be sent
await createMemberWithSubscription('month', 500, 'usd', '2000-01-10');
await createMemberWithSubscription('month', 500, 'eur', '2000-01-10');
await createMemberWithSubscription('year', 1000, 'usd', '2000-01-10');
const fourthResultPromise = milestonesService.initAndRun(100);
await clock.tickAsync(100);
const fourthRun = await fourthResultPromise;
assert(fourthRun.members === undefined);
assert(fourthRun.arr.value === 100);
assert(fourthRun.arr.emailSentAt !== undefined);
const arrMilestoneModel = await models.Milestone.findOne({value: 100, type: 'arr'});
assert.ok(arrMilestoneModel.get('id'));
assert.equal(arrMilestoneModel.get('type'), 'arr');
assert.equal(arrMilestoneModel.get('value'), 100);
assert.ok(arrMilestoneModel.get('created_at'));
assert.equal(arrMilestoneModel.get('email_sent_at'), null);
assert(loggingStub.called);
});
it('Does not run ARR milestones when Stripe is not live enabled', async function () {
mockManager.mockSetting('stripe_publishable_key', 'pk_test_89843uihsidfh98832uo8ri');
mockManager.mockSetting('stripe_connect_publishable_key', 'pk_test_89843uihsidfh98832uo8ri');
await createFreeMembers(12);
const resultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const result = await resultPromise;
assert(result.members.value === 20);
assert(result.members.emailSentAt !== undefined);
assert(result.arr === undefined);
const memberMilestoneModel = await models.Milestone.findOne({value: 20, type: 'members'});
assert.ok(memberMilestoneModel.get('id'));
assert.equal(memberMilestoneModel.get('type'), 'members');
assert.equal(memberMilestoneModel.get('value'), 20);
assert.ok(memberMilestoneModel.get('created_at'));
assert.equal(memberMilestoneModel.get('email_sent_at'), null);
assert(loggingStub.called);
});
it('Does not send emails for milestones when imported members present', async function () {
mockManager.mockSetting('stripe_publishable_key', 'pk_live_89843uihsidfh98832uo8ri');
mockManager.mockSetting('stripe_connect_publishable_key', 'pk_test_89843uihsidfh98832uo8ri');
await createFreeMembers(10, 1);
const resultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const result = await resultPromise;
assert(result.members.value === 30);
assert(result.members.emailSentAt === null);
assert(result.arr === undefined);
const memberMilestoneModel = await models.Milestone.findOne({value: 30, type: 'members'});
assert.ok(memberMilestoneModel.get('id'));
assert.equal(memberMilestoneModel.get('type'), 'members');
assert.equal(memberMilestoneModel.get('value'), 30);
assert.ok(memberMilestoneModel.get('created_at'));
assert.equal(memberMilestoneModel.get('email_sent_at'), null);
const arrMilestoneModel = await models.Milestone.findOne({value: 150, type: 'arr'});
assert.equal(arrMilestoneModel, null);
assert(loggingStub.called);
});
it('Does not run when milestoneEmails labs flag is not set', async function () {
mockManager.mockLabsDisabled('milestoneEmails');
const resultPromise = milestonesService.initAndRun(fifteenDays);
await clock.tickAsync(fifteenDays);
const result = await resultPromise;
assert(result === undefined);
assert(loggingStub.called);
});
});