0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00
ghost/core/test/unit/migration_spec.js
Katharina Irrgang d81bc91bd2 Error creation (#7477)
refs #7116, refs #2001

- Changes the way Ghost errors are implemented to benefit from proper inheritance
- Moves all error definitions into a single file
- Changes the error constructor to take an options object, rather than needing the arguments to be passed in the correct order.
- Provides a wrapper so that any errors that haven't already been converted to GhostErrors get converted before they are displayed.

Summary of changes:

* 🐛  set NODE_ENV in config handler
*   add GhostError implementation (core/server/errors.js)
  - register all errors in one file
  - inheritance from GhostError
  - option pattern
* 🔥  remove all error files
*   wrap all errors into GhostError in case of HTTP
* 🎨  adaptions
  - option pattern for errors
  - use GhostError when needed
* 🎨  revert debug deletion and add TODO for error id's
2016-10-06 13:27:35 +01:00

249 lines
10 KiB
JavaScript

var should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
_ = require('lodash'),
Promise = require('bluebird'),
crypto = require('crypto'),
fs = require('fs'),
errors = require('../../server/errors'),
models = require('../../server/models'),
exporter = require('../../server/data/export'),
schema = require('../../server/data/schema'),
migration = rewire('../../server/data/migration'),
fixtures = require('../../server/data/migration/fixtures'),
populate = require('../../server/data/migration/populate'),
update = rewire('../../server/data/migration/update'),
defaultSettings = schema.defaultSettings,
schemaTables = Object.keys(schema.tables),
sandbox = sinon.sandbox.create();
// Check version integrity
// These tests exist to ensure that developers are not able to modify the database schema, or permissions fixtures
// without knowing that they also need to update the default database version,
// both of which are required for migrations to work properly.
describe.skip('DB version integrity', function () {
// Only these variables should need updating
var currentDbVersion = 'alpha.1',
currentSchemaHash = 'b3bdae210526b2d4393359c3e45d7f83',
currentFixturesHash = '30b0a956b04e634e7f2cddcae8d2fd20';
// If this test is failing, then it is likely a change has been made that requires a DB version bump,
// and the values above will need updating as confirmation
it('should not change without fixing this test', function () {
var tablesNoValidation = _.cloneDeep(schema.tables),
schemaHash,
fixturesHash;
_.each(tablesNoValidation, function (table) {
return _.each(table, function (column, name) {
table[name] = _.omit(column, 'validations');
});
});
schemaHash = crypto.createHash('md5').update(JSON.stringify(tablesNoValidation)).digest('hex');
fixturesHash = crypto.createHash('md5').update(JSON.stringify(fixtures.fixtures)).digest('hex');
// Test!
defaultSettings.core.databaseVersion.defaultValue.should.eql(currentDbVersion);
schemaHash.should.eql(currentSchemaHash);
fixturesHash.should.eql(currentFixturesHash);
schema.versioning.canMigrateFromVersion.should.eql('003');
});
});
describe('Migrations', function () {
var loggerStub, resetLogger;
before(function () {
models.init();
});
afterEach(function () {
sandbox.restore();
resetLogger();
});
beforeEach(function () {
loggerStub = {
info: sandbox.stub(),
warn: sandbox.stub()
};
resetLogger = update.__set__('logger', loggerStub);
});
describe('Backup', function () {
var exportStub, filenameStub, fsStub;
beforeEach(function () {
exportStub = sandbox.stub(exporter, 'doExport').returns(new Promise.resolve());
filenameStub = sandbox.stub(exporter, 'fileName').returns(new Promise.resolve('test'));
fsStub = sandbox.stub(fs, 'writeFile').yields();
});
it('should create a backup JSON file', function (done) {
migration.backupDatabase(loggerStub).then(function () {
exportStub.calledOnce.should.be.true();
filenameStub.calledOnce.should.be.true();
fsStub.calledOnce.should.be.true();
loggerStub.info.calledTwice.should.be.true();
done();
}).catch(done);
});
it('should fall back to console.log if no logger provided', function (done) {
var noopStub = sandbox.stub(_, 'noop');
migration.backupDatabase().then(function () {
exportStub.calledOnce.should.be.true();
filenameStub.calledOnce.should.be.true();
fsStub.calledOnce.should.be.true();
noopStub.calledTwice.should.be.true();
// restore early so we get the test output
noopStub.restore();
done();
}).catch(done);
});
});
describe('Reset', function () {
var deleteStub;
beforeEach(function () {
deleteStub = sandbox.stub(schema.commands, 'deleteTable').returns(new Promise.resolve());
});
it('should delete all tables in reverse order', function (done) {
migration.reset().then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length);
// First call should be called with the last table
deleteStub.firstCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
// Last call should be called with the first table
deleteStub.lastCall.calledWith(schemaTables[0]).should.be.true();
done();
}).catch(done);
});
it('should delete all tables in reverse order when called twice in a row', function (done) {
migration.reset().then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length);
// First call should be called with the last table
deleteStub.firstCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
// Last call should be called with the first table
deleteStub.lastCall.calledWith(schemaTables[0]).should.be.true();
return migration.reset();
}).then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length * 2);
// First call (second set) should be called with the last table
deleteStub.getCall(schemaTables.length).calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
// Last call (second Set) should be called with the first table
deleteStub.getCall(schemaTables.length * 2 - 1).calledWith(schemaTables[0]).should.be.true();
done();
}).catch(done);
});
});
describe('Populate', function () {
var createStub, fixturesStub;
beforeEach(function () {
fixturesStub = sandbox.stub(fixtures, 'populate').returns(new Promise.resolve());
});
it('should create all tables, and populate fixtures', function (done) {
createStub = sandbox.stub(schema.commands, 'createTable').returns(new Promise.resolve());
populate().then(function (result) {
should.not.exist(result);
createStub.called.should.be.true();
createStub.callCount.should.be.eql(schemaTables.length);
createStub.firstCall.calledWith(schemaTables[0]).should.be.true();
createStub.lastCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
fixturesStub.calledOnce.should.be.true();
done();
}).catch(done);
});
it('should rollback if error occurs', function (done) {
var i = 0;
createStub = sandbox.stub(schema.commands, 'createTable', function () {
i = i + 1;
if (i > 10) {
return new Promise.reject(new Error('error on table creation :('));
}
return new Promise.resolve();
});
populate()
.then(function () {
done(new Error('should throw an error for database population'));
})
.catch(function (err) {
should.exist(err);
(err instanceof errors.GhostError).should.eql(true);
createStub.callCount.should.eql(11);
done();
});
});
});
describe('isDatabaseOutOfDate', function () {
var updateDatabaseSchemaStub, updateDatabaseSchemaReset, versionsSpy;
beforeEach(function () {
versionsSpy = sandbox.spy(schema.versioning, 'getMigrationVersions');
// For these tests, stub out the actual update task
updateDatabaseSchemaStub = sandbox.stub().returns(new Promise.resolve());
updateDatabaseSchemaReset = update.__set__('updateDatabaseSchema', updateDatabaseSchemaStub);
});
afterEach(function () {
updateDatabaseSchemaReset();
});
it('should throw error if versions are too old', function () {
var response = update.isDatabaseOutOfDate({fromVersion: '000', toVersion: '002'});
updateDatabaseSchemaStub.calledOnce.should.be.false();
(response.error instanceof errors.DatabaseVersionError).should.eql(true);
});
it('should just return if versions are the same', function () {
var migrateToDatabaseVersionStub = sandbox.stub().returns(new Promise.resolve()),
migrateToDatabaseVersionReset = update.__set__('migrateToDatabaseVersion', migrateToDatabaseVersionStub),
response = update.isDatabaseOutOfDate({fromVersion: '004', toVersion: '004'});
response.migrate.should.eql(false);
versionsSpy.calledOnce.should.be.false();
migrateToDatabaseVersionStub.callCount.should.eql(0);
migrateToDatabaseVersionReset();
});
it('should throw an error if the database version is higher than the default', function () {
var response = update.isDatabaseOutOfDate({fromVersion: '010', toVersion: '004'});
updateDatabaseSchemaStub.calledOnce.should.be.false();
(response.error instanceof errors.DatabaseVersionError).should.eql(true);
});
});
});