mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-27 22:49:56 -05:00
132726fe20
refs https://github.com/TryGhost/Team/issues/1470 Instead of counting the MRR by resolving all the deltas from the past until now, we should start with the current calculated MRR and resolve it until the first event. That would give a more accurate recent MRR (in exchange for a less accurate MRR for older data) and allows us to limit the amount of returned days in the future. - Includes MRR stats service that can fetch the current MRR per currency - The service can return a history of the MRR for every day and currency - New admin API endpoint /stats/mrr that returns the MRR history - Includes tests for these new service and endpoint
234 lines
6.6 KiB
JavaScript
234 lines
6.6 KiB
JavaScript
const statsService = require('../../../../core/server/services/stats');
|
|
const {agentProvider, fixtureManager} = require('../../../utils/e2e-framework');
|
|
const moment = require('moment');
|
|
require('should');
|
|
const nock = require('nock');
|
|
|
|
let agent;
|
|
let counter = 0;
|
|
|
|
async function createMemberWithSubscription(interval, amount, currency, date) {
|
|
counter += 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: 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, body) => {
|
|
const [match, resource, id] = 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();
|
|
}
|
|
|
|
describe('MRR Stats Service', function () {
|
|
before(async function () {
|
|
agent = await agentProvider.getAdminAPIAgent();
|
|
await fixtureManager.init();
|
|
await agent.loginAsOwner();
|
|
});
|
|
|
|
afterEach(function () {
|
|
nock.cleanAll();
|
|
});
|
|
|
|
describe('getCurrentMrr', function () {
|
|
it('Always returns at least one currency', async function () {
|
|
const result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'usd',
|
|
mrr: 0
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('Can handle multiple currencies', async function () {
|
|
await createMemberWithSubscription('month', 500, 'eur', '2000-01-10');
|
|
const result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('Increases MRR by 1 / 12 of yearly subscriptions', async function () {
|
|
await createMemberWithSubscription('year', 12, 'usd', '2000-01-10');
|
|
const result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 1
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('Increases MRR with monthly subscriptions', async function () {
|
|
await createMemberWithSubscription('month', 1, 'usd', '2000-01-11');
|
|
const result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 2
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('Floors results', async function () {
|
|
await createMemberWithSubscription('year', 17, 'usd', '2000-01-12');
|
|
let result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 3
|
|
}
|
|
]);
|
|
|
|
// Floor 11/12 to 0 (same as getMRRDelta method)
|
|
await createMemberWithSubscription('year', 11, 'usd', '2000-01-12');
|
|
result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 3
|
|
}
|
|
]);
|
|
|
|
// Floor 11/12 to 0, don't combine with previous addition
|
|
await createMemberWithSubscription('year', 11, 'usd', '2000-01-12');
|
|
result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 3
|
|
}
|
|
]);
|
|
|
|
// Floor 13/12 to 1
|
|
await createMemberWithSubscription('year', 13, 'usd', '2000-01-12');
|
|
result = await statsService.mrr.getCurrentMrr();
|
|
result.should.eql([
|
|
{
|
|
currency: 'eur',
|
|
mrr: 500
|
|
},
|
|
{
|
|
currency: 'usd',
|
|
mrr: 4
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('fetchAllDeltas', function () {
|
|
it('Returns deltas in ascending order', async function () {
|
|
const results = await statsService.mrr.fetchAllDeltas();
|
|
results.length.should.equal(4);
|
|
results.should.match([
|
|
{
|
|
date: '2000-01-10',
|
|
delta: 500,
|
|
currency: 'eur'
|
|
},
|
|
{
|
|
date: '2000-01-10',
|
|
delta: 1,
|
|
currency: 'usd'
|
|
},
|
|
{
|
|
date: '2000-01-11',
|
|
delta: 1,
|
|
currency: 'usd'
|
|
},
|
|
{
|
|
date: '2000-01-12',
|
|
delta: 2,
|
|
currency: 'usd'
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
});
|