mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-19 16:42:17 +03:00
89493893d1
- 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
311 lines
11 KiB
JavaScript
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);
|
|
});
|
|
});
|