Ghost/ghost/data-generator/test/data-generator.test.js
Sam Lord 4ff467794f Entirely rewrote data generator to simplify codebase
refs: https://github.com/TryGhost/DevOps/issues/11

This is a pretty huge commit, but the relevant points are:
* Each importer no longer needs to be passed a set of data, it just gets the data it needs
* Each importer specifies its dependencies, so that the order of import can be determined at runtime using a topological sort
* The main data generator function can just tell each importer to import the data it has

This makes working on the data generator much easier.

Some other benefits are:
* Batched importing, massively speeding up the whole process
* `--tables` to set the exact tables you want to import, and specify the quantity of each
2023-08-04 13:36:09 +01:00

247 lines
8.0 KiB
JavaScript

// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');
const knex = require('knex');
const importers = require('../lib/importers');
const ProductsImporter = importers.find(i => i.table === 'products');
const StripeProductsImporter = importers.find(i => i.table === 'stripe_products');
const StripePricesImporter = importers.find(i => i.table === 'stripe_prices');
const generateEvents = require('../lib/utils/event-generator');
const DataGenerator = require('../index');
const schema = require('../../core/core/server/data/schema');
describe('Data Generator', function () {
let db;
beforeEach(async function () {
db = knex({
client: 'sqlite3',
useNullAsDefault: true,
connection: {
filename: ':memory:'
}
});
for (const tableName of Object.keys(schema.tables)) {
await db.schema.createTable(tableName, function (table) {
for (const rowName of Object.keys(schema.tables[tableName])) {
const row = schema.tables[tableName][rowName];
if (rowName === '@@UNIQUE_CONSTRAINTS@@') {
for (const constraints of row) {
table.unique(constraints);
}
break;
} else if (rowName === '@@INDEXES@@') {
for (const indexes of row) {
table.index(indexes);
}
break;
}
let rowChain = table[row.type.toLowerCase()](rowName);
if ('nullable' in row) {
if (row.nullable) {
rowChain = rowChain.nullable();
} else {
rowChain = rowChain.notNullable();
}
}
if ('defaultTo' in row) {
rowChain = rowChain.defaultTo(row.defaultTo);
}
if ('references' in row) {
const [foreignTable, foreignRow] = row.references.split('.');
rowChain = rowChain.references(foreignRow).inTable(foreignTable);
}
if (row.unique) {
table.unique([rowName]);
}
if (row.primary) {
table.primary(rowName);
}
}
});
}
});
afterEach(async function () {
await db.destroy();
});
it('Can import the whole dataset without error', async function () {
const dataGenerator = new DataGenerator({
eventsOnly: false,
knex: db,
schema: schema,
logger: {
info: () => { },
ok: () => { }
},
tables: [{
name: 'members',
quantity: 10
}, {
name: 'posts',
quantity: 2
}],
withDefault: true
});
try {
return await dataGenerator.importData();
} catch (err) {
(false).should.eql(true, err.message);
}
});
});
describe('Importer', function () {
let db;
beforeEach(async function () {
db = knex({
client: 'sqlite3',
useNullAsDefault: true,
connection: {
filename: ':memory:'
}
});
await db.schema.createTable('products', function (table) {
table.string('id');
table.string('name');
table.string('slug');
table.string('visibility');
table.date('created_at');
table.string('type');
table.string('description');
table.string('currency');
table.integer('monthly_price');
table.integer('yearly_price');
table.string('monthly_price_id');
table.string('yearly_price_id');
});
await db.schema.createTable('stripe_products', function (table) {
table.string('id');
table.string('product_id');
table.string('stripe_product_id');
table.date('created_at');
table.date('updated_at');
});
await db.schema.createTable('stripe_prices', function (table) {
table.string('id');
table.string('stripe_price_id');
table.string('stripe_product_id');
table.boolean('active');
table.string('nickname');
table.string('currency');
table.integer('amount');
table.string('type');
table.string('interval');
table.string('description');
table.date('created_at');
table.date('updated_at');
});
});
afterEach(async function () {
await db.destroy();
});
it('Should import a single item', async function () {
const transaction = await db.transaction();
const productsImporter = new ProductsImporter(db, transaction);
await productsImporter.import();
transaction.commit();
const products = await db.select('id', 'name').from('products');
products.length.should.eql(4);
products[0].name.should.eql('Free');
});
it('Should import an item for each entry in an array', async function () {
const transaction = await db.transaction();
const productsImporter = new ProductsImporter(db, transaction);
await productsImporter.import();
const stripeProductsImporter = new StripeProductsImporter(db, transaction);
await stripeProductsImporter.import();
transaction.commit();
const results = await db.select('id').from('stripe_products');
results.length.should.eql(4);
});
it('Should update products to reference price ids', async function () {
const transaction = await db.transaction();
const productsImporter = new ProductsImporter(db, transaction);
await productsImporter.import();
const stripeProductsImporter = new StripeProductsImporter(db, transaction);
await stripeProductsImporter.import();
const stripePricesImporter = new StripePricesImporter(db, transaction);
await stripePricesImporter.import();
await productsImporter.finalise();
await stripeProductsImporter.finalise();
await stripePricesImporter.finalise();
transaction.commit();
const results = await db.select('id', 'name', 'monthly_price_id', 'yearly_price_id').from('products');
results.length.should.eql(4);
results[0].name.should.eql('Free');
});
});
describe('Events Generator', function () {
it('Generates a set of timestamps which meet the criteria', function () {
const startTime = new Date();
startTime.setDate(startTime.getDate() - 30);
const endTime = new Date();
const timestamps = generateEvents({
shape: 'flat',
total: 100,
trend: 'positive',
startTime,
endTime
});
for (const timestamp of timestamps) {
timestamp.valueOf().should.be.lessThanOrEqual(endTime.valueOf());
timestamp.valueOf().should.be.greaterThanOrEqual(startTime.valueOf());
}
});
it('Works for a set of shapes', function () {
const startTime = new Date();
startTime.setDate(startTime.getDate() - 30);
const endTime = new Date();
const options = {
startTime,
endTime,
total: 100,
trend: 'positive'
};
const shapes = ['linear', 'flat', 'ease-in', 'ease-out'];
for (const shape of shapes) {
try {
generateEvents(Object.assign({}, options, {shape}));
} catch (err) {
(false).should.eql(true, err.message);
}
}
});
});