/*globals describe, afterEach, it*/ /*jshint expr:true*/ var should = require('should'), sinon = require('sinon'), Promise = require('bluebird'), _ = require('lodash'), testUtils = require('../utils'), config = require('../../server/config'), // Stuff we are testing ImportManager = require('../../server/data/importer'), JSONHandler = require('../../server/data/importer/handlers/json'), ImageHandler = require('../../server/data/importer/handlers/image'), DataImporter = require('../../server/data/importer/importers/data'), ImageImporter = require('../../server/data/importer/importers/image'), sandbox = sinon.sandbox.create(), storage = require('../../server/storage'); // To stop jshint complaining should.equal(true, true); describe('Importer', function () { afterEach(function () { sandbox.restore(); }); describe('ImportManager', function () { it('has the correct interface', function () { ImportManager.handlers.should.be.instanceof(Array).and.have.lengthOf(2); ImportManager.importers.should.be.instanceof(Array).and.have.lengthOf(2); ImportManager.loadFile.should.be.instanceof(Function); ImportManager.preProcess.should.be.instanceof(Function); ImportManager.doImport.should.be.instanceof(Function); ImportManager.generateReport.should.be.instanceof(Function); }); it('gets the correct extensions', function () { ImportManager.getExtensions().should.be.instanceof(Array).and.have.lengthOf(8); ImportManager.getExtensions().should.containEql('.json'); ImportManager.getExtensions().should.containEql('.zip'); ImportManager.getExtensions().should.containEql('.jpg'); }); it('gets the correct types', function () { ImportManager.getTypes().should.be.instanceof(Array).and.have.lengthOf(8); ImportManager.getTypes().should.containEql('application/octet-stream'); ImportManager.getTypes().should.containEql('application/json'); ImportManager.getTypes().should.containEql('application/zip'); ImportManager.getTypes().should.containEql('application/x-zip-compressed'); }); it('globs extensions correctly', function () { ImportManager.getGlobPattern(JSONHandler).should.equal('**/*+(.json)'); }); // Step 1 of importing is loadFile describe('loadFile', function () { it('knows when to process a file', function (done) { var testFile = {name: 'myFile.json', path: '/my/path/myFile.json'}, zipSpy = sandbox.stub(ImportManager, 'processZip').returns(Promise.resolve()), fileSpy = sandbox.stub(ImportManager, 'processFile').returns(Promise.resolve()), cleanSpy = sandbox.stub(ImportManager, 'cleanUp').returns(Promise.resolve()); ImportManager.loadFile(testFile).then(function () { zipSpy.calledOnce.should.be.false; fileSpy.calledOnce.should.be.true; cleanSpy.calledOnce.should.be.true; done(); }); }); // We need to make sure we don't actually extract a zip and leave temporary files everywhere! it('knows when to process a zip', function (done) { var testZip = {name: 'myFile.zip', path: '/my/path/myFile.zip'}, zipSpy = sandbox.stub(ImportManager, 'processZip').returns(Promise.resolve()), fileSpy = sandbox.stub(ImportManager, 'processFile').returns(Promise.resolve()), cleanSpy = sandbox.stub(ImportManager, 'cleanUp').returns(Promise.resolve()); ImportManager.loadFile(testZip).then(function () { zipSpy.calledOnce.should.be.true; fileSpy.calledOnce.should.be.false; cleanSpy.calledOnce.should.be.true; done(); }); }); it('has same result for zips and files', function (done) { var testFile = {name: 'myFile.json', path: '/my/path/myFile.json'}, testZip = {name: 'myFile.zip', path: '/my/path/myFile.zip'}, // need to stub out the extract and glob function for zip extractSpy = sandbox.stub(ImportManager, 'extractZip').returns(Promise.resolve('/tmp/dir/')), getFileSpy = sandbox.stub(ImportManager, 'getFilesFromZip'), jsonSpy = sandbox.stub(JSONHandler, 'loadFile').returns(Promise.resolve({posts: []})), imageSpy = sandbox.stub(ImageHandler, 'loadFile'), cleanSpy = sandbox.stub(ImportManager, 'cleanUp').returns(Promise.resolve()); getFileSpy.withArgs(JSONHandler).returns(['/tmp/dir/myFile.json']); getFileSpy.withArgs(ImageHandler).returns([]); ImportManager.processZip(testZip).then(function (zipResult) { extractSpy.calledOnce.should.be.true; getFileSpy.calledTwice.should.be.true; jsonSpy.calledOnce.should.be.true; imageSpy.called.should.be.false; cleanSpy.calledOnce.should.be.true; ImportManager.processFile(testFile, '.json').then(function (fileResult) { jsonSpy.calledTwice.should.be.true; // They should both have data keys, and they should be equivalent zipResult.should.have.property('data'); fileResult.should.have.property('data'); zipResult.should.eql(fileResult); done(); }); }); }); }); // Step 2 of importing is preProcess describe('preProcess', function () { // preProcess can modify the data prior to importing it('calls the DataImporter preProcess method', function (done) { var input = {data: {}, images: []}, // pass a copy so that input doesn't get modified inputCopy = _.cloneDeep(input), dataSpy = sandbox.spy(DataImporter, 'preProcess'); ImportManager.preProcess(inputCopy).then(function (output) { dataSpy.calledOnce.should.be.true; dataSpy.calledWith(inputCopy).should.be.true; // eql checks for equality // equal checks the references are for the same object output.should.not.equal(input); output.should.have.property('preProcessedByData', true); done(); }); }); }); // Step 3 of importing is doImport describe('doImport', function () { // doImport calls the real importers and has an effect on the DB. We don't want any of those calls to be made, // but to test that the right calls would be made it('calls the DataImporter doImport method with the data object', function (done) { var input = {data: {posts: []}, images: []}, // pass a copy so that input doesn't get modified inputCopy = _.cloneDeep(input), dataSpy = sandbox.stub(DataImporter, 'doImport', function (i) { return Promise.resolve(i); }), imageSpy = sandbox.stub(ImageImporter, 'doImport', function (i) { return Promise.resolve(i); }), // The data importer should get the data object expectedData = input.data, expectedImages = input.images; ImportManager.doImport(inputCopy).then(function (output) { // eql checks for equality // equal checks the references are for the same object dataSpy.calledOnce.should.be.true; imageSpy.calledOnce.should.be.true; dataSpy.getCall(0).args[0].should.eql(expectedData); imageSpy.getCall(0).args[0].should.eql(expectedImages); // we stubbed this as a noop but ImportManager calls with sequence, so we should get an array output.should.eql([expectedImages, expectedData]); done(); }); }); }); // Step 4 of importing is generateReport describe('generateReport', function () { // generateReport is intended to create a message to show to the user about what has been imported // it is currently a noop it('is currently a noop', function (done) { var input = {data: {}, images: []}; ImportManager.generateReport(input).then(function (output) { output.should.equal(input); done(); }); }); }); }); describe('JSONHandler', function () { it('has the correct interface', function () { JSONHandler.type.should.eql('data'); JSONHandler.extensions.should.be.instanceof(Array).and.have.lengthOf(1); JSONHandler.extensions.should.containEql('.json'); JSONHandler.types.should.be.instanceof(Array).and.have.lengthOf(2); JSONHandler.types.should.containEql('application/octet-stream'); JSONHandler.types.should.containEql('application/json'); JSONHandler.loadFile.should.be.instanceof(Function); }); it('correctly handles a valid db api wrapper', function (done) { var file = [{ path: testUtils.fixtures.getExportFixturePath('export-003-api-wrapper'), name: 'export-003-api-wrapper.json' }]; JSONHandler.loadFile(file).then(function (result) { _.keys(result).should.containEql('meta'); _.keys(result).should.containEql('data'); done(); }); }); it('correctly errors when given a bad db api wrapper', function (done) { var file = [{ path: testUtils.fixtures.getExportFixturePath('export-003-api-wrapper-bad'), name: 'export-003-api-wrapper-bad.json' }]; JSONHandler.loadFile(file).then(function () { done(new Error('Didn\'t error for bad db api wrapper')); }).catch(function (response) { response.type.should.equal('BadRequestError'); done(); }); }); }); describe('ImageHandler', function () { var origConfig = _.cloneDeep(config), storage = require('../../server/storage'), store = storage.getStorage(); afterEach(function () { config.set(_.merge({}, origConfig)); }); it('has the correct interface', function () { ImageHandler.type.should.eql('images'); ImageHandler.extensions.should.be.instanceof(Array).and.have.lengthOf(6); ImageHandler.extensions.should.containEql('.jpg'); ImageHandler.extensions.should.containEql('.jpeg'); ImageHandler.extensions.should.containEql('.gif'); ImageHandler.extensions.should.containEql('.png'); ImageHandler.extensions.should.containEql('.svg'); ImageHandler.extensions.should.containEql('.svgz'); ImageHandler.types.should.be.instanceof(Array).and.have.lengthOf(4); ImageHandler.types.should.containEql('image/jpeg'); ImageHandler.types.should.containEql('image/png'); ImageHandler.types.should.containEql('image/gif'); ImageHandler.types.should.containEql('image/svg+xml'); ImageHandler.loadFile.should.be.instanceof(Function); }); it('can load a single file', function (done) { var filename = 'test-image.jpeg', file = [{ path: '/my/test/' + filename, name: filename }], storeSpy = sandbox.spy(store, 'getUniqueFileName'), storageSpy = sandbox.spy(storage, 'getStorage'); ImageHandler.loadFile(_.clone(file)).then(function () { storageSpy.calledOnce.should.be.true; storeSpy.calledOnce.should.be.true; storeSpy.firstCall.args[1].originalPath.should.equal('test-image.jpeg'); storeSpy.firstCall.args[1].targetDir.should.match(/\/content\/images$/); storeSpy.firstCall.args[1].newPath.should.eql('/content/images/test-image.jpeg'); done(); }); }); it('can load a single file, maintaining structure', function (done) { var filename = 'photos/my-cat.jpeg', file = [{ path: '/my/test/' + filename, name: filename }], storeSpy = sandbox.spy(store, 'getUniqueFileName'), storageSpy = sandbox.spy(storage, 'getStorage'); ImageHandler.loadFile(_.clone(file)).then(function () { storageSpy.calledOnce.should.be.true; storeSpy.calledOnce.should.be.true; storeSpy.firstCall.args[1].originalPath.should.equal('photos/my-cat.jpeg'); storeSpy.firstCall.args[1].targetDir.should.match(/\/content\/images\/photos$/); storeSpy.firstCall.args[1].newPath.should.eql('/content/images/photos/my-cat.jpeg'); done(); }); }); it('can load a single file, removing ghost dirs', function (done) { var filename = 'content/images/my-cat.jpeg', file = [{ path: '/my/test/content/images/' + filename, name: filename }], storeSpy = sandbox.spy(store, 'getUniqueFileName'), storageSpy = sandbox.spy(storage, 'getStorage'); ImageHandler.loadFile(_.clone(file)).then(function () { storageSpy.calledOnce.should.be.true; storeSpy.calledOnce.should.be.true; storeSpy.firstCall.args[1].originalPath.should.equal('content/images/my-cat.jpeg'); storeSpy.firstCall.args[1].targetDir.should.match(/\/content\/images$/); storeSpy.firstCall.args[1].newPath.should.eql('/content/images/my-cat.jpeg'); done(); }); }); it('can load a file (subdirectory)', function (done) { config.set({url: 'http://testurl.com/subdir'}); var filename = 'test-image.jpeg', file = [{ path: '/my/test/' + filename, name: filename }], storeSpy = sandbox.spy(store, 'getUniqueFileName'), storageSpy = sandbox.spy(storage, 'getStorage'); ImageHandler.loadFile(_.clone(file)).then(function () { storageSpy.calledOnce.should.be.true; storeSpy.calledOnce.should.be.true; storeSpy.firstCall.args[1].originalPath.should.equal('test-image.jpeg'); storeSpy.firstCall.args[1].targetDir.should.match(/\/content\/images$/); storeSpy.firstCall.args[1].newPath.should.eql('/subdir/content/images/test-image.jpeg'); done(); }); }); it('can load multiple files', function (done) { var files = [{ path: '/my/test/testing.png', name: 'testing.png' }, { path: '/my/test/photo/kitten.jpg', name: 'photo/kitten.jpg' }, { path: '/my/test/content/images/animated/bunny.gif', name: 'content/images/animated/bunny.gif' }, { path: '/my/test/images/puppy.jpg', name: 'images/puppy.jpg' }], storeSpy = sandbox.spy(store, 'getUniqueFileName'), storageSpy = sandbox.spy(storage, 'getStorage'); ImageHandler.loadFile(_.clone(files)).then(function () { storageSpy.calledOnce.should.be.true; storeSpy.callCount.should.eql(4); storeSpy.firstCall.args[1].originalPath.should.equal('testing.png'); storeSpy.firstCall.args[1].targetDir.should.match(/\/content\/images$/); storeSpy.firstCall.args[1].newPath.should.eql('/content/images/testing.png'); storeSpy.secondCall.args[1].originalPath.should.equal('photo/kitten.jpg'); storeSpy.secondCall.args[1].targetDir.should.match(/\/content\/images\/photo$/); storeSpy.secondCall.args[1].newPath.should.eql('/content/images/photo/kitten.jpg'); storeSpy.thirdCall.args[1].originalPath.should.equal('content/images/animated/bunny.gif'); storeSpy.thirdCall.args[1].targetDir.should.match(/\/content\/images\/animated$/); storeSpy.thirdCall.args[1].newPath.should.eql('/content/images/animated/bunny.gif'); storeSpy.lastCall.args[1].originalPath.should.equal('images/puppy.jpg'); storeSpy.lastCall.args[1].targetDir.should.match(/\/content\/images$/); storeSpy.lastCall.args[1].newPath.should.eql('/content/images/puppy.jpg'); done(); }); }); }); describe('DataImporter', function () { it('has the correct interface', function () { DataImporter.type.should.eql('data'); DataImporter.preProcess.should.be.instanceof(Function); DataImporter.doImport.should.be.instanceof(Function); }); }); describe('ImageImporter', function () { it('has the correct interface', function () { ImageImporter.type.should.eql('images'); ImageImporter.preProcess.should.be.instanceof(Function); ImageImporter.doImport.should.be.instanceof(Function); }); it('does preprocess posts correctly', function () { var inputData = require('../utils/fixtures/import/import-data-1.json'), outputData = ImageImporter.preProcess(_.cloneDeep(inputData)); inputData.data.data.posts[0].markdown.should.not.containEql('/content/images/my-image.png'); inputData.data.data.posts[0].html.should.not.containEql('/content/images/my-image.png'); outputData.data.data.posts[0].markdown.should.containEql('/content/images/my-image.png'); outputData.data.data.posts[0].html.should.containEql('/content/images/my-image.png'); inputData.data.data.posts[0].markdown.should.not.containEql('/content/images/photos/cat.jpg'); inputData.data.data.posts[0].html.should.not.containEql('/content/images/photos/cat.jpg'); outputData.data.data.posts[0].markdown.should.containEql('/content/images/photos/cat.jpg'); outputData.data.data.posts[0].html.should.containEql('/content/images/photos/cat.jpg'); inputData.data.data.posts[0].image.should.eql('/images/my-image.png'); outputData.data.data.posts[0].image.should.eql('/content/images/my-image.png'); }); it('does import the images correctly', function () { var inputData = require('../utils/fixtures/import/import-data-1.json'), storageApi = { save: sandbox.stub().returns(Promise.resolve()) }, storageSpy = sandbox.stub(storage, 'getStorage', function () { return storageApi; }); ImageImporter.doImport(inputData.images).then(function () { storageSpy.calledOnce.should.be.true; storageApi.save.calledTwice.should.be.true; }); }); }); });