Ghost/ghost/stats-service/test/lib/mrr.test.js
Fabien "egg" O'Carroll 104f84f252 Added eslint rule for file naming convention
As discussed with the product team we want to enforce kebab-case file names for
all files, with the exception of files which export a single class, in which
case they should be PascalCase and reflect the class which they export.

This will help find classes faster, and should push better naming for them too.

Some files and packages have been excluded from this linting, specifically when
a library or framework depends on the naming of a file for the functionality
e.g. Ember, knex-migrator, adapter-manager
2023-05-09 12:34:34 -04:00

391 lines
12 KiB
JavaScript

const MrrStatsService = require('../../lib/MrrStatsService');
const moment = require('moment');
const sinon = require('sinon');
const knex = require('knex').default;
require('should');
describe('MrrStatsService', function () {
describe('getHistory', function () {
/** @type {MrrStatsService} */
let mrrStatsService;
const today = '2000-01-10';
const tomorrow = '2000-01-11';
const yesterday = '2000-01-09';
const dayBeforeYesterday = '2000-01-08';
const twoDaysBeforeYesterday = '2000-01-07';
after(function () {
sinon.restore();
});
/** @type {import('knex').Knex} */
let db;
before(function () {
const todayDate = moment(today).toDate();
sinon.useFakeTimers(todayDate.getTime());
});
beforeEach(async function () {
db = knex({client: 'sqlite3', connection: {filename: ':memory:'}, useNullAsDefault: true});
mrrStatsService = new MrrStatsService({knex: db});
await db.schema.createTable('members_paid_subscription_events', function (table) {
table.string('currency');
table.string('mrr_delta');
table.date('created_at');
});
await db.schema.createTable('members_stripe_customers_subscriptions', function (table) {
table.string('plan_currency');
table.string('mrr');
});
});
afterEach(async function () {
await db.destroy();
});
it('Handles no data', async function () {
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(1);
// Note that currencies should always be sorted ascending, so EUR should be first.
results[0].should.eql({
date: today,
mrr: 0,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 0,
currency: 'usd'
}
]);
});
it('Always returns at least one value', async function () {
await db('members_stripe_customers_subscriptions').insert([{
plan_currency: 'usd',
mrr: 1
}, {
plan_currency: 'eur',
mrr: 2
}]);
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(2);
// Note that currencies should always be sorted ascending, so EUR should be first.
results[0].should.eql({
date: today,
mrr: 2,
currency: 'eur'
});
results[1].should.eql({
date: today,
mrr: 1,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 2,
currency: 'eur'
},
{
mrr: 1,
currency: 'usd'
}
]);
});
it('Does not substract delta of first event', async function () {
await db('members_stripe_customers_subscriptions').insert([{
plan_currency: 'usd',
mrr: 5
}]);
await db('members_paid_subscription_events').insert([{
created_at: today,
mrr_delta: 5,
currency: 'usd'
}]);
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(2);
results[0].should.eql({
date: yesterday,
mrr: 0,
currency: 'usd'
});
results[1].should.eql({
date: today,
mrr: 5,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 5,
currency: 'usd'
}
]);
});
it('Correctly calculates deltas', async function () {
await db('members_paid_subscription_events').insert([{
created_at: yesterday,
mrr_delta: 2,
currency: 'usd'
},
{
created_at: today,
mrr_delta: 5,
currency: 'usd'
}]);
await db('members_stripe_customers_subscriptions').insert([{
plan_currency: 'usd',
mrr: 2
}, {
plan_currency: 'usd',
mrr: 5
}]);
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(3);
results[0].should.eql({
date: dayBeforeYesterday,
mrr: 0,
currency: 'usd'
});
results[1].should.eql({
date: yesterday,
mrr: 2,
currency: 'usd'
});
results[2].should.eql({
date: today,
mrr: 7,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 7,
currency: 'usd'
}
]);
});
it('Correctly calculates deltas for multiple currencies', async function () {
await db('members_paid_subscription_events').insert([
{
created_at: yesterday,
mrr_delta: 200,
currency: 'eur'
},
{
created_at: yesterday,
mrr_delta: 2,
currency: 'usd'
},
{
created_at: today,
mrr_delta: 800,
currency: 'eur'
},
{
created_at: today,
mrr_delta: 5,
currency: 'usd'
}
]);
await db('members_stripe_customers_subscriptions').insert([{
plan_currency: 'eur',
mrr: 200
}, {
plan_currency: 'usd',
mrr: 2
}, {
plan_currency: 'eur',
mrr: 800
}, {
plan_currency: 'usd',
mrr: 5
}, {
plan_currency: 'eur',
mrr: 200
}]);
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(6);
results[0].should.eql({
date: dayBeforeYesterday,
mrr: 200,
currency: 'eur'
});
results[1].should.eql({
date: dayBeforeYesterday,
mrr: 0,
currency: 'usd'
});
results[2].should.eql({
date: yesterday,
mrr: 400,
currency: 'eur'
});
results[3].should.eql({
date: yesterday,
mrr: 2,
currency: 'usd'
});
results[4].should.eql({
date: today,
mrr: 1200,
currency: 'eur'
});
results[5].should.eql({
date: today,
mrr: 7,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 1200,
currency: 'eur'
},
{
mrr: 7,
currency: 'usd'
}
]);
});
it('Ignores invalid currencies in deltas', async function () {
await db('members_paid_subscription_events').insert({
created_at: today,
mrr_delta: 200,
currency: 'abc'
});
await db('members_stripe_customers_subscriptions').insert({
plan_currency: 'usd',
mrr: 7
});
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(1);
results[0].should.eql({
date: yesterday,
mrr: 7,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 7,
currency: 'usd'
}
]);
});
it('Ignores events in the future', async function () {
await db('members_paid_subscription_events').insert([
{
created_at: yesterday,
mrr_delta: 2,
currency: 'usd'
},
{
created_at: today,
mrr_delta: 5,
currency: 'usd'
},
{
created_at: tomorrow,
mrr_delta: 10,
currency: 'usd'
}
]);
await db('members_stripe_customers_subscriptions').insert({plan_currency: 'usd', mrr: 7});
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(3);
results[0].should.eql({
date: dayBeforeYesterday,
mrr: 0,
currency: 'usd'
});
results[1].should.eql({
date: yesterday,
mrr: 2,
currency: 'usd'
});
results[2].should.eql({
date: today,
mrr: 7,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 7,
currency: 'usd'
}
]);
});
it('Correctly handles negative total MRR', async function () {
await db('members_paid_subscription_events').insert([
{
created_at: dayBeforeYesterday,
mrr_delta: 2,
currency: 'usd'
},
{
created_at: yesterday,
mrr_delta: -1000,
currency: 'usd'
},
{
created_at: today,
mrr_delta: 1000,
currency: 'usd'
}
]);
await db('members_stripe_customers_subscriptions').insert({plan_currency: 'usd', mrr: 7});
const {data: results, meta} = await mrrStatsService.getHistory();
results.length.should.eql(4);
results[0].should.eql({
date: twoDaysBeforeYesterday,
mrr: 5,
currency: 'usd'
});
results[1].should.eql({
date: dayBeforeYesterday,
// We are mainly testing that this should not be 1000!
mrr: 7,
currency: 'usd'
});
results[2].should.eql({
date: yesterday,
// Should never be shown negative (in fact it is -993 here)
mrr: 0,
currency: 'usd'
});
results[3].should.eql({
date: today,
mrr: 7,
currency: 'usd'
});
meta.totals.should.eql([
{
mrr: 7,
currency: 'usd'
}
]);
});
});
});