/*globals describe, before, beforeEach, afterEach, it*/ /*jshint expr:true*/ var testUtils = require('../utils/index'), should = require('should'), sinon = require('sinon'), when = require('when'), assert = require('assert'), _ = require('lodash'), rewire = require('rewire'), // Stuff we are testing config = rewire('../../server/config'), configUpdate = config.__get__('updateConfig'), defaultConfig = rewire('../../../config.example')[process.env.NODE_ENV], migration = rewire('../../server/data/migration'), versioning = require('../../server/data/versioning'), exporter = require('../../server/data/export'), importer = require('../../server/data/import'), Importer000 = require('../../server/data/import/000'), Importer001 = require('../../server/data/import/001'), Importer002 = require('../../server/data/import/002'), Importer003 = require('../../server/data/import/003'), knex, sandbox = sinon.sandbox.create(); describe('Import', function () { before(testUtils.teardown); afterEach(testUtils.teardown); afterEach(function () { sandbox.restore(); }); should.exist(exporter); should.exist(importer); describe('Resolves', function () { beforeEach(testUtils.setup()); beforeEach(function () { var newConfig = _.extend(config, defaultConfig); migration.__get__('config', newConfig); configUpdate(newConfig); knex = config.database.knex; }); it('resolves 000', function (done) { var importStub = sandbox.stub(Importer000, 'importData', function () { return when.resolve(); }), fakeData = { test: true }; importer('000', fakeData).then(function () { importStub.calledWith(fakeData).should.equal(true); importStub.restore(); done(); }).catch(done); }); it('resolves 001', function (done) { var importStub = sandbox.stub(Importer001, 'importData', function () { return when.resolve(); }), fakeData = { test: true }; importer('001', fakeData).then(function () { importStub.calledWith(fakeData).should.equal(true); importStub.restore(); done(); }).catch(done); }); it('resolves 002', function (done) { var importStub = sandbox.stub(Importer002, 'importData', function () { return when.resolve(); }), fakeData = { test: true }; importer('002', fakeData).then(function () { importStub.calledWith(fakeData).should.equal(true); importStub.restore(); done(); }).catch(done); }); it('resolves 003', function (done) { var importStub = sandbox.stub(Importer003, 'importData', function () { return when.resolve(); }), fakeData = { test: true }; importer('003', fakeData).then(function () { importStub.calledWith(fakeData).should.equal(true); importStub.restore(); done(); }).catch(done); }); }); describe('000', function () { beforeEach(testUtils.setup('owner', 'settings')); should.exist(Importer000); it('imports data from 000', function (done) { var exportData, versioningStub = sandbox.stub(versioning, 'getDatabaseVersion', function () { return when.resolve('000'); }); testUtils.fixtures.loadExportFixture('export-000').then(function (exported) { exportData = exported; return importer('000', exportData); }).then(function () { // Grab the data from tables return when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]); }).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // we always have 1 user, the owner user we added users.length.should.equal(1, 'There should only be one user'); // import no longer requires all data to be dropped, and adds posts posts.length.should.equal(exportData.data.posts.length, 'Wrong number of posts'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); // test tags tags.length.should.equal(exportData.data.tags.length, 'no new tags'); versioningStub.restore(); done(); }).catch(done); }); }); describe('001', function () { beforeEach(testUtils.setup('owner', 'settings')); should.exist(Importer001); it('safely imports data from 001', function (done) { var exportData, timestamp = 1349928000000; testUtils.fixtures.loadExportFixture('export-001').then(function (exported) { exportData = exported; // Modify timestamp data for testing exportData.data.posts[0].created_at = timestamp; exportData.data.posts[0].updated_at = timestamp; exportData.data.posts[0].published_at = timestamp; return importer('001', exportData); }).then(function () { // Grab the data from tables return when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]); }).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3], exportEmail; // we always have 1 user, the default user we added users.length.should.equal(1, 'There should only be one user'); // user should still have the credentials from the original insert, not the import users[0].email.should.equal(testUtils.DataGenerator.Content.users[0].email); users[0].password.should.equal(testUtils.DataGenerator.Content.users[0].password); // but the name, slug, and bio should have been overridden users[0].name.should.equal(exportData.data.users[0].name); users[0].slug.should.equal(exportData.data.users[0].slug); users[0].bio.should.equal(exportData.data.users[0].bio); // import no longer requires all data to be dropped, and adds posts posts.length.should.equal(exportData.data.posts.length, 'Wrong number of posts'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); // activeTheme should NOT have been overridden _.findWhere(settings, {key: 'activeTheme'}).value.should.equal('casper', 'Wrong theme'); // email address should have been overridden exportEmail = _.findWhere(exportData.data.settings, {key: 'email'}).value; _.findWhere(settings, {key: 'email'}).value.should.equal(exportEmail, 'Wrong email in settings'); // test tags tags.length.should.equal(exportData.data.tags.length, 'no new tags'); // Ensure imported post retains set timestamp // When in sqlite we are returned a unix timestamp number, // in MySQL we're returned a date object. // We pass the returned post always through the date object // to ensure the return is consistent for all DBs. assert.equal(new Date(posts[0].created_at).getTime(), timestamp); assert.equal(new Date(posts[0].updated_at).getTime(), timestamp); assert.equal(new Date(posts[0].published_at).getTime(), timestamp); done(); }).catch(done); }); it('doesn\'t import invalid post data from 001', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-001').then(function (exported) { exportData = exported; //change title to 151 characters exportData.data.posts[0].title = new Array(152).join('a'); exportData.data.posts[0].tags = 'Tag'; return importer('001', exportData); }).then(function () { (1).should.eql(0, 'Data import should not resolve promise.'); }, function (error) { error[0].message.should.eql('Value in [posts.title] exceeds maximum length of 150 characters.'); error[0].type.should.eql('ValidationError'); when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // we always have 1 user, the default user we added users.length.should.equal(1, 'There should only be one user'); // Nothing should have been imported posts.length.should.equal(0, 'Wrong number of posts'); tags.length.should.equal(0, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }); }).catch(done); }); it('doesn\'t import invalid settings data from 001', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-001').then(function (exported) { exportData = exported; //change to blank settings key exportData.data.settings[3].key = null; return importer('001', exportData); }).then(function () { (1).should.eql(0, 'Data import should not resolve promise.'); }, function (error) { error[0].message.should.eql('Value in [settings.key] cannot be blank.'); error[0].type.should.eql('ValidationError'); when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // we always have 1 user, the default user we added users.length.should.equal(1, 'There should only be one user'); // Nothing should have been imported posts.length.should.equal(0, 'Wrong number of posts'); tags.length.should.equal(0, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }); }).catch(done); }); }); describe('002', function () { beforeEach(testUtils.setup('owner', 'settings')); should.exist(Importer002); it('safely imports data from 002', function (done) { var exportData, timestamp = 1349928000000; testUtils.fixtures.loadExportFixture('export-002').then(function (exported) { exportData = exported; // Modify timestamp data for testing exportData.data.posts[0].created_at = timestamp; exportData.data.posts[0].updated_at = timestamp; exportData.data.posts[0].published_at = timestamp; return importer('002', exportData); }).then(function () { // Grab the data from tables return when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]); }).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3], exportEmail; // we always have 1 user, the owner user we added users.length.should.equal(1, 'There should only be one user'); // user should still have the credentials from the original insert, not the import users[0].email.should.equal(testUtils.DataGenerator.Content.users[0].email); users[0].password.should.equal(testUtils.DataGenerator.Content.users[0].password); // but the name, slug, and bio should have been overridden users[0].name.should.equal(exportData.data.users[0].name); users[0].slug.should.equal(exportData.data.users[0].slug); users[0].bio.should.equal(exportData.data.users[0].bio); // import no longer requires all data to be dropped, and adds posts posts.length.should.equal(exportData.data.posts.length, 'Wrong number of posts'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); // activeTheme should NOT have been overridden _.findWhere(settings, {key: 'activeTheme'}).value.should.equal('casper', 'Wrong theme'); // email address should have been overridden exportEmail = _.findWhere(exportData.data.settings, {key: 'email'}).value; _.findWhere(settings, {key: 'email'}).value.should.equal(exportEmail, 'Wrong email in settings'); // test tags tags.length.should.equal(exportData.data.tags.length, 'no new tags'); // Ensure imported post retains set timestamp // When in sqlite we are returned a unix timestamp number, // in MySQL we're returned a date object. // We pass the returned post always through the date object // to ensure the return is consistant for all DBs. assert.equal(new Date(posts[0].created_at).getTime(), timestamp); assert.equal(new Date(posts[0].updated_at).getTime(), timestamp); assert.equal(new Date(posts[0].published_at).getTime(), timestamp); done(); }).catch(function (error) { done(new Error(error)); }); }); it('doesn\'t import invalid post data from 002', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-002').then(function (exported) { exportData = exported; //change title to 151 characters exportData.data.posts[0].title = new Array(152).join('a'); exportData.data.posts[0].tags = 'Tag'; return importer('002', exportData); }).then(function () { (1).should.eql(0, 'Data import should not resolve promise.'); }, function (error) { error[0].message.should.eql('Value in [posts.title] exceeds maximum length of 150 characters.'); error[0].type.should.eql('ValidationError'); when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // we always have 1 user, the owner user we added users.length.should.equal(1, 'There should only be one user'); // Nothing should have been imported posts.length.should.equal(0, 'Wrong number of posts'); tags.length.should.equal(0, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }); }).catch(done); }); it('doesn\'t import invalid settings data from 002', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-002').then(function (exported) { exportData = exported; //change to blank settings key exportData.data.settings[3].key = null; return importer('002', exportData); }).then(function () { (1).should.eql(0, 'Data import should not resolve promise.'); }, function (error) { error[0].message.should.eql('Value in [settings.key] cannot be blank.'); error[0].type.should.eql('ValidationError'); when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // we always have 1 user, the owner user we added users.length.should.equal(1, 'There should only be one user'); // Nothing should have been imported posts.length.should.equal(0, 'Wrong number of posts'); tags.length.should.equal(0, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }); }).catch(done); }); }); describe('003', function () { knex = config.database.knex; beforeEach(testUtils.setup('roles', 'owner', 'settings')); should.exist(Importer003); it('safely imports data from 003 (single user)', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003').then(function (exported) { exportData = exported; return importer('003', exportData); }).then(function () { // Grab the data from tables return when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select() ]); }).then(function (importedData) { should.exist(importedData); importedData.length.should.equal(4, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3]; // user should still have the credentials from the original insert, not the import users[0].email.should.equal(testUtils.DataGenerator.Content.users[0].email); users[0].password.should.equal(testUtils.DataGenerator.Content.users[0].password); // but the name, slug, and bio should have been overridden users[0].name.should.equal(exportData.data.users[0].name); users[0].slug.should.equal(exportData.data.users[0].slug); users[0].bio.should.equal(exportData.data.users[0].bio); // test posts posts.length.should.equal(1, 'Wrong number of posts'); // test tags tags.length.should.equal(1, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }).catch(done); }); it('safely imports data from 003 (multi user)', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003-mu').then(function (exported) { exportData = exported; return importer('003', exportData); }).then(function () { // Grab the data from tables return when.all([ knex('users').select(), knex('posts').select(), knex('settings').select(), knex('tags').select(), knex('roles_users').select() ]); }).then(function (importedData) { should.exist(importedData); var user2, user3; importedData.length.should.equal(5, 'Did not get data successfully'); var users = importedData[0], posts = importedData[1], settings = importedData[2], tags = importedData[3], roles_users = importedData[4]; // we imported 2 users, the original user should be untouched users.length.should.equal(3, 'There should only be three users'); users[0].email.should.equal(testUtils.DataGenerator.Content.users[0].email); users[0].password.should.equal(testUtils.DataGenerator.Content.users[0].password); // the other two users should have the imported data, but they get inserted in different orders user2 = _.find(users, function (user) { return user.name === 'Josephine Bloggs'; }); user3 = _.find(users, function (user) { return user.name === 'Smith Wellingsworth'; }); user2.email.should.equal(exportData.data.users[0].email); user3.email.should.equal(exportData.data.users[1].email); roles_users.length.should.equal(2, 'There should be 3 role relations'); _.each(roles_users, function (roleUser) { if (roleUser.user_id === user2.id) { roleUser.role_id.should.equal(1, 'Josephine should be an admin'); } if (roleUser.user_id === user3.id) { roleUser.role_id.should.equal(3, 'Smith should be an author by default'); } }); // test posts posts.length.should.equal(1, 'Wrong number of posts'); // test tags tags.length.should.equal(1, 'no new tags'); // test settings settings.length.should.be.above(0, 'Wrong number of settings'); _.findWhere(settings, {key: 'databaseVersion'}).value.should.equal('003', 'Wrong database version'); done(); }).catch(done); }); it('handles validation errors nicely', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003-badValidation').then(function (exported) { exportData = exported; return importer('003', exportData); }).catch(function (response) { response.length.should.equal(5); response[0].type.should.equal('ValidationError'); response[0].message.should.eql('Value in [posts.title] cannot be blank.'); response[1].type.should.equal('ValidationError'); response[1].message.should.eql('Value in [posts.slug] cannot be blank.'); response[2].type.should.equal('ValidationError'); response[2].message.should.eql('Value in [settings.key] cannot be blank.'); response[3].type.should.equal('ValidationError'); response[3].message.should.eql('Value in [tags.slug] cannot be blank.'); response[4].type.should.equal('ValidationError'); response[4].message.should.eql('Value in [tags.name] cannot be blank.'); done(); }).catch(done); }); it('handles database errors nicely', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003-dbErrors').then(function (exported) { exportData = exported; return importer('003', exportData); }).catch(function (response) { response.length.should.equal(3); response[0].type.should.equal('DataImportError'); response[0].message.should.eql( 'Duplicate entry found. Multiple values of "tagging-things" found for tags.slug.' ); response[1].type.should.equal('DataImportError'); response[1].message.should.eql( 'Duplicate entry found. Multiple values of "tagging-things" found for tags.slug.' ); response[2].type.should.equal('DataImportError'); response[2].message.should.eql( 'Duplicate entry found. Multiple values of "test-ghost-post" found for posts.slug.' ); done(); }).catch(done); }); it('doesn\'t import invalid tags data from 003', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003-nullTags').then(function (exported) { exportData = exported; exportData.data.tags.length.should.be.above(1); exportData.data.posts_tags.length.should.be.above(1); return importer('003', exportData); }).then(function () { done(new Error('Allowed import of invalid tags data')); }).catch(function (response) { response.length.should.equal(4); response[0].type.should.equal('ValidationError'); response[0].message.should.eql('Value in [tags.uuid] cannot be blank.'); response[1].type.should.equal('ValidationError'); response[1].message.should.eql('Validation (isUUID) failed for uuid'); response[2].type.should.equal('ValidationError'); response[2].message.should.eql('Value in [tags.name] cannot be blank.'); response[3].type.should.equal('ValidationError'); response[3].message.should.eql('Value in [tags.slug] cannot be blank.'); done(); }).catch(done); }); it('doesn\'t import invalid posts data from 003', function (done) { var exportData; testUtils.fixtures.loadExportFixture('export-003-nullPosts').then(function (exported) { exportData = exported; exportData.data.posts.length.should.be.above(1); return importer('003', exportData); }).then(function () { done(new Error('Allowed import of invalid tags data')); }).catch(function (response) { response.length.should.equal(6); done(); }).catch(done); }); }); });