Ghost/test/utils/db-utils.js
Daniel Lockyer a3cc66be50 Copied DB for faster SQLite tests
refs https://github.com/TryGhost/Toolbox/issues/136

- we nuke and reinitialize the DB many times between tests
- this forms a good portion of the time taken and we shouldn't be spending
  so much time on just resetting the DB back to a known state
- this commit switches out the knex-migrator reset + init calls for SQLite to
  a function which keeps a clean copy of the DB and copies the file back
  when we "reset"
- for MySQL, existing functionality is kept
- this massively speeds up tests because it saves ~700ms+ for every reset
- whilst this change seems to work, it's just the start so there's a
  lot more refactoring needed. this change is currently gated to CI until
  it's deemed safe/sane enough to run on local machines without blowing
  up
2021-12-06 15:22:44 +00:00

136 lines
4.3 KiB
JavaScript

const debug = require('@tryghost/debug')('test:dbUtils');
// Utility Packages
const fs = require('fs-extra');
const Promise = require('bluebird');
const KnexMigrator = require('knex-migrator');
const knexMigrator = new KnexMigrator();
// Ghost Internals
const config = require('../../core/shared/config');
const db = require('../../core/server/data/db');
const schema = require('../../core/server/data/schema').tables;
const schemaTables = Object.keys(schema);
// Other Test Utilities
const urlServiceUtils = require('./url-service-utils');
const dbHash = Date.now();
module.exports.reset = async () => {
// Only run this copy in CI until it gets fleshed out
if (process.env.CI && config.get('database:client') === 'sqlite3') {
const filename = config.get('database:connection:filename');
const filenameOrig = `${filename}.${dbHash}-orig`;
const dbExists = await fs.pathExists(filenameOrig);
if (dbExists) {
await fs.copyFile(filenameOrig, filename);
} else {
await knexMigrator.reset({force: true});
// Do a full database initialisation
await knexMigrator.init();
await fs.copyFile(filename, filenameOrig);
}
} else {
await knexMigrator.reset({force: true});
// Do a full database initialisation
await knexMigrator.init();
}
};
module.exports.initData = async () => {
await knexMigrator.init();
await urlServiceUtils.reset();
await urlServiceUtils.init();
await urlServiceUtils.isFinished();
};
module.exports.truncate = async (tableName) => {
if (config.get('database:client') === 'sqlite3') {
const [foreignKeysEnabled] = await db.knex.raw('PRAGMA foreign_keys;');
if (foreignKeysEnabled.foreign_keys) {
await db.knex.raw('PRAGMA foreign_keys = OFF;');
}
await db.knex(tableName).truncate();
if (foreignKeysEnabled.foreign_keys) {
await db.knex.raw('PRAGMA foreign_keys = ON;');
}
return;
}
await db.knex.raw('SET FOREIGN_KEY_CHECKS=0;');
await db.knex(tableName).truncate();
await db.knex.raw('SET FOREIGN_KEY_CHECKS=1;');
};
// we must always try to delete all tables
module.exports.clearData = async () => {
debug('Database reset');
await knexMigrator.reset({force: true});
urlServiceUtils.reset();
};
/**
* Has to run in a transaction for MySQL, otherwise the foreign key check does not work.
* Sqlite3 has no truncate command.
*/
module.exports.teardown = () => {
debug('Database teardown');
urlServiceUtils.reset();
const tables = schemaTables.concat(['migrations']);
if (config.get('database:client') === 'sqlite3') {
return Promise
.mapSeries(tables, function createTable(table) {
return (async function () {
const [foreignKeysEnabled] = await db.knex.raw('PRAGMA foreign_keys;');
if (foreignKeysEnabled.foreign_keys) {
await db.knex.raw('PRAGMA foreign_keys = OFF;');
}
await db.knex.raw('DELETE FROM ' + table + ';');
if (foreignKeysEnabled.foreign_keys) {
await db.knex.raw('PRAGMA foreign_keys = ON;');
}
})();
})
.catch(function (err) {
// CASE: table does not exist
if (err.errno === 1) {
return Promise.resolve();
}
throw err;
})
.finally(() => {
debug('Database teardown end');
});
}
return db.knex.transaction(function (trx) {
return db.knex.raw('SET FOREIGN_KEY_CHECKS=0;').transacting(trx)
.then(function () {
return Promise
.each(tables, function createTable(table) {
return db.knex.raw('TRUNCATE ' + table + ';').transacting(trx);
});
})
.then(function () {
return db.knex.raw('SET FOREIGN_KEY_CHECKS=1;').transacting(trx);
})
.catch(function (err) {
// CASE: table does not exist
if (err.errno === 1146) {
return Promise.resolve();
}
throw err;
});
});
};