mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +03:00
742 lines
35 KiB
JavaScript
742 lines
35 KiB
JavaScript
const errors = require('@tryghost/errors');
|
|
const should = require('should');
|
|
const sinon = require('sinon');
|
|
const rewire = require('rewire');
|
|
const Promise = require('bluebird');
|
|
const _ = require('lodash');
|
|
const testUtils = require('../../../utils');
|
|
const moment = require('moment');
|
|
const path = require('path');
|
|
|
|
// Stuff we are testing
|
|
const ImportManager = require('../../../../core/server/data/importer');
|
|
|
|
const JSONHandler = require('../../../../core/server/data/importer/handlers/json');
|
|
let ImageHandler = rewire('../../../../core/server/data/importer/handlers/image');
|
|
const MarkdownHandler = require('../../../../core/server/data/importer/handlers/markdown');
|
|
const DataImporter = require('../../../../core/server/data/importer/importers/data');
|
|
const ImageImporter = require('../../../../core/server/data/importer/importers/image');
|
|
const storage = require('../../../../core/server/adapters/storage');
|
|
const urlUtils = require('../../../utils/urlUtils');
|
|
|
|
describe('Importer', function () {
|
|
afterEach(function () {
|
|
sinon.restore();
|
|
ImageHandler = rewire('../../../../core/server/data/importer/handlers/image');
|
|
});
|
|
|
|
describe('ImportManager', function () {
|
|
it('has the correct interface', function () {
|
|
ImportManager.handlers.should.be.instanceof(Array).and.have.lengthOf(3);
|
|
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(11);
|
|
ImportManager.getExtensions().should.containEql('.json');
|
|
ImportManager.getExtensions().should.containEql('.zip');
|
|
ImportManager.getExtensions().should.containEql('.jpg');
|
|
ImportManager.getExtensions().should.containEql('.md');
|
|
});
|
|
|
|
it('gets the correct types', function () {
|
|
ImportManager.getContentTypes().should.be.instanceof(Array).and.have.lengthOf(12);
|
|
ImportManager.getContentTypes().should.containEql('application/octet-stream');
|
|
ImportManager.getContentTypes().should.containEql('application/json');
|
|
ImportManager.getContentTypes().should.containEql('application/zip');
|
|
ImportManager.getContentTypes().should.containEql('application/x-zip-compressed');
|
|
ImportManager.getContentTypes().should.containEql('text/plain');
|
|
});
|
|
|
|
it('gets the correct directories', function () {
|
|
ImportManager.getDirectories().should.be.instanceof(Array).and.have.lengthOf(2);
|
|
ImportManager.getDirectories().should.containEql('images');
|
|
ImportManager.getDirectories().should.containEql('content');
|
|
});
|
|
|
|
it('globs extensions correctly', function () {
|
|
ImportManager.getGlobPattern(ImportManager.getExtensions())
|
|
.should.equal('+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico|.json|.md|.markdown|.zip)');
|
|
ImportManager.getGlobPattern(ImportManager.getDirectories())
|
|
.should.equal('+(images|content)');
|
|
ImportManager.getGlobPattern(JSONHandler.extensions)
|
|
.should.equal('+(.json)');
|
|
ImportManager.getGlobPattern(ImageHandler.extensions)
|
|
.should.equal('+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico)');
|
|
ImportManager.getExtensionGlob(ImportManager.getExtensions())
|
|
.should.equal('*+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico|.json|.md|.markdown|.zip)');
|
|
ImportManager.getDirectoryGlob(ImportManager.getDirectories())
|
|
.should.equal('+(images|content)');
|
|
ImportManager.getExtensionGlob(ImportManager.getExtensions(), 0)
|
|
.should.equal('*+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico|.json|.md|.markdown|.zip)');
|
|
ImportManager.getDirectoryGlob(ImportManager.getDirectories(), 0)
|
|
.should.equal('+(images|content)');
|
|
ImportManager.getExtensionGlob(ImportManager.getExtensions(), 1)
|
|
.should.equal('{*/*,*}+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico|.json|.md|.markdown|.zip)');
|
|
ImportManager.getDirectoryGlob(ImportManager.getDirectories(), 1)
|
|
.should.equal('{*/,}+(images|content)');
|
|
ImportManager.getExtensionGlob(ImportManager.getExtensions(), 2)
|
|
.should.equal('**/*+(.jpg|.jpeg|.gif|.png|.svg|.svgz|.ico|.json|.md|.markdown|.zip)');
|
|
ImportManager.getDirectoryGlob(ImportManager.getDirectories(), 2)
|
|
.should.equal('**/+(images|content)');
|
|
});
|
|
|
|
// Step 1 of importing is loadFile
|
|
describe('loadFile', function () {
|
|
it('knows when to process a file', function (done) {
|
|
const testFile = {name: 'myFile.json', path: '/my/path/myFile.json'};
|
|
const zipSpy = sinon.stub(ImportManager, 'processZip').returns(Promise.resolve());
|
|
const fileSpy = sinon.stub(ImportManager, 'processFile').returns(Promise.resolve());
|
|
|
|
ImportManager.loadFile(testFile).then(function () {
|
|
zipSpy.calledOnce.should.be.false();
|
|
fileSpy.calledOnce.should.be.true();
|
|
done();
|
|
}).catch(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) {
|
|
const testZip = {name: 'myFile.zip', path: '/my/path/myFile.zip'};
|
|
const zipSpy = sinon.stub(ImportManager, 'processZip').returns(Promise.resolve());
|
|
const fileSpy = sinon.stub(ImportManager, 'processFile').returns(Promise.resolve());
|
|
|
|
ImportManager.loadFile(testZip).then(function () {
|
|
zipSpy.calledOnce.should.be.true();
|
|
fileSpy.calledOnce.should.be.false();
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('has same result for zips and files', function (done) {
|
|
const testFile = {name: 'myFile.json', path: '/my/path/myFile.json'};
|
|
const testZip = {name: 'myFile.zip', path: '/my/path/myFile.zip'};
|
|
|
|
// need to stub out the extract and glob function for zip
|
|
const extractSpy = sinon.stub(ImportManager, 'extractZip').returns(Promise.resolve('/tmp/dir/'));
|
|
|
|
const validSpy = sinon.stub(ImportManager, 'isValidZip').returns(true);
|
|
const baseDirSpy = sinon.stub(ImportManager, 'getBaseDirectory').returns();
|
|
const getFileSpy = sinon.stub(ImportManager, 'getFilesFromZip');
|
|
const jsonSpy = sinon.stub(JSONHandler, 'loadFile').returns(Promise.resolve({posts: []}));
|
|
const imageSpy = sinon.stub(ImageHandler, 'loadFile');
|
|
const mdSpy = sinon.stub(MarkdownHandler, 'loadFile');
|
|
|
|
getFileSpy.returns([]);
|
|
getFileSpy.withArgs(JSONHandler).returns(['/tmp/dir/myFile.json']);
|
|
|
|
ImportManager.processZip(testZip).then(function (zipResult) {
|
|
extractSpy.calledOnce.should.be.true();
|
|
validSpy.calledOnce.should.be.true();
|
|
baseDirSpy.calledOnce.should.be.true();
|
|
getFileSpy.calledThrice.should.be.true();
|
|
jsonSpy.calledOnce.should.be.true();
|
|
imageSpy.called.should.be.false();
|
|
mdSpy.called.should.be.false();
|
|
|
|
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();
|
|
});
|
|
}).catch(done);
|
|
});
|
|
|
|
describe('Validate Zip', function () {
|
|
it('accepts a zip with a base directory', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-with-base-dir');
|
|
|
|
ImportManager.isValidZip(testDir).should.be.ok();
|
|
});
|
|
|
|
it('accepts a zip without a base directory', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-without-base-dir');
|
|
|
|
ImportManager.isValidZip(testDir).should.be.ok();
|
|
});
|
|
|
|
it('accepts a zip with an image directory', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-image-dir');
|
|
|
|
ImportManager.isValidZip(testDir).should.be.ok();
|
|
});
|
|
|
|
it('fails a zip with two base directories', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-with-double-base-dir');
|
|
|
|
ImportManager.isValidZip.bind(ImportManager, testDir).should.throw(errors.UnsupportedMediaTypeError);
|
|
});
|
|
|
|
it('fails a zip with no content', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-invalid');
|
|
|
|
ImportManager.isValidZip.bind(ImportManager, testDir).should.throw(errors.UnsupportedMediaTypeError);
|
|
});
|
|
|
|
it('shows a special error for old Roon exports', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-old-roon-export');
|
|
|
|
const msg = 'Your zip file looks like an old format Roon export, ' +
|
|
'please re-export your Roon blog and try again.';
|
|
|
|
ImportManager.isValidZip.bind(ImportManager, testDir).should.throw(errors.UnsupportedMediaTypeError);
|
|
ImportManager.isValidZip.bind(ImportManager, testDir).should.throw(msg);
|
|
});
|
|
});
|
|
|
|
describe('Get Base Dir', function () {
|
|
it('returns string for base directory', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-with-base-dir');
|
|
|
|
ImportManager.getBaseDirectory(testDir).should.equal('basedir');
|
|
});
|
|
|
|
it('returns empty for no base directory', function () {
|
|
const testDir = path.resolve('test/utils/fixtures/import/zips/zip-without-base-dir');
|
|
|
|
should.not.exist(ImportManager.getBaseDirectory(testDir));
|
|
});
|
|
});
|
|
|
|
describe('Zip behaviour', function () {
|
|
it('can call extract and error correctly', function () {
|
|
return ImportManager
|
|
// Deliberately pass something that can't be extracted just to check this method signature is working
|
|
.extractZip('test/utils/fixtures/import/zips/zip-with-base-dir')
|
|
.then((res) => {
|
|
throw new Error('should have failed');
|
|
})
|
|
.catch((err) => {
|
|
err.message.should.match(/EISDIR/);
|
|
err.code.should.match(/EISDIR/);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// 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) {
|
|
const input = {data: {}, images: []};
|
|
|
|
// pass a copy so that input doesn't get modified
|
|
const inputCopy = _.cloneDeep(input);
|
|
|
|
const dataSpy = sinon.spy(DataImporter, 'preProcess');
|
|
const imageSpy = sinon.spy(ImageImporter, 'preProcess');
|
|
|
|
ImportManager.preProcess(inputCopy).then(function (output) {
|
|
dataSpy.calledOnce.should.be.true();
|
|
dataSpy.calledWith(inputCopy).should.be.true();
|
|
imageSpy.calledOnce.should.be.true();
|
|
imageSpy.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);
|
|
output.should.have.property('preProcessedByImage', true);
|
|
done();
|
|
}).catch(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) {
|
|
const input = {data: {posts: []}, images: []};
|
|
|
|
// pass a copy so that input doesn't get modified
|
|
const inputCopy = _.cloneDeep(input);
|
|
|
|
const dataSpy = sinon.stub(DataImporter, 'doImport').callsFake(function (i) {
|
|
return Promise.resolve(i);
|
|
});
|
|
|
|
const imageSpy = sinon.stub(ImageImporter, 'doImport').callsFake(function (i) {
|
|
return Promise.resolve(i);
|
|
});
|
|
|
|
// The data importer should get the data object
|
|
const expectedData = input.data;
|
|
|
|
const 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();
|
|
}).catch(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) {
|
|
const input = {data: {}, images: []};
|
|
ImportManager.generateReport(input).then(function (output) {
|
|
output.should.equal(input);
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('importFromFile', function () {
|
|
it('does the import steps in order', function (done) {
|
|
const loadFileSpy = sinon.stub(ImportManager, 'loadFile').returns(Promise.resolve());
|
|
const preProcessSpy = sinon.stub(ImportManager, 'preProcess').returns(Promise.resolve());
|
|
const doImportSpy = sinon.stub(ImportManager, 'doImport').returns(Promise.resolve());
|
|
const generateReportSpy = sinon.stub(ImportManager, 'generateReport').returns(Promise.resolve());
|
|
const cleanupSpy = sinon.stub(ImportManager, 'cleanUp').returns({});
|
|
|
|
ImportManager.importFromFile({}).then(function () {
|
|
loadFileSpy.calledOnce.should.be.true();
|
|
preProcessSpy.calledOnce.should.be.true();
|
|
doImportSpy.calledOnce.should.be.true();
|
|
generateReportSpy.calledOnce.should.be.true();
|
|
cleanupSpy.calledOnce.should.be.true();
|
|
sinon.assert.callOrder(loadFileSpy, preProcessSpy, doImportSpy, generateReportSpy, cleanupSpy);
|
|
|
|
done();
|
|
}).catch(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.contentTypes.should.be.instanceof(Array).and.have.lengthOf(2);
|
|
JSONHandler.contentTypes.should.containEql('application/octet-stream');
|
|
JSONHandler.contentTypes.should.containEql('application/json');
|
|
JSONHandler.loadFile.should.be.instanceof(Function);
|
|
});
|
|
|
|
it('correctly handles a valid db api wrapper', function (done) {
|
|
const file = [{
|
|
path: testUtils.fixtures.getExportFixturePath('valid'),
|
|
name: 'valid.json'
|
|
}];
|
|
JSONHandler.loadFile(file).then(function (result) {
|
|
_.keys(result).should.containEql('meta');
|
|
_.keys(result).should.containEql('data');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('correctly errors when given a bad db api wrapper', function (done) {
|
|
const file = [{
|
|
path: testUtils.fixtures.getExportFixturePath('broken'),
|
|
name: 'broken.json'
|
|
}];
|
|
|
|
JSONHandler.loadFile(file).then(function () {
|
|
done(new Error('Didn\'t error for bad db api wrapper'));
|
|
}).catch(function (response) {
|
|
response.errorType.should.equal('BadRequestError');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('ImageHandler', function () {
|
|
const store = storage.getStorage();
|
|
|
|
it('has the correct interface', function () {
|
|
ImageHandler.type.should.eql('images');
|
|
ImageHandler.extensions.should.be.instanceof(Array).and.have.lengthOf(7);
|
|
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.extensions.should.containEql('.ico');
|
|
ImageHandler.contentTypes.should.be.instanceof(Array).and.have.lengthOf(6);
|
|
ImageHandler.contentTypes.should.containEql('image/jpeg');
|
|
ImageHandler.contentTypes.should.containEql('image/png');
|
|
ImageHandler.contentTypes.should.containEql('image/gif');
|
|
ImageHandler.contentTypes.should.containEql('image/svg+xml');
|
|
ImageHandler.contentTypes.should.containEql('image/x-icon');
|
|
ImageHandler.contentTypes.should.containEql('image/vnd.microsoft.icon');
|
|
ImageHandler.loadFile.should.be.instanceof(Function);
|
|
});
|
|
|
|
it('can load a single file', function (done) {
|
|
const filename = 'test-image.jpeg';
|
|
|
|
const file = [{
|
|
path: '/my/test/' + filename,
|
|
name: filename
|
|
}];
|
|
|
|
const storeSpy = sinon.spy(store, 'getUniqueFileName');
|
|
const storageSpy = sinon.spy(storage, 'getStorage');
|
|
|
|
ImageHandler.loadFile(_.clone(file)).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storeSpy.calledOnce.should.be.true();
|
|
storeSpy.firstCall.args[0].originalPath.should.equal('test-image.jpeg');
|
|
storeSpy.firstCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images$/);
|
|
storeSpy.firstCall.args[0].newPath.should.eql('/content/images/test-image.jpeg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can load a single file, maintaining structure', function (done) {
|
|
const filename = 'photos/my-cat.jpeg';
|
|
|
|
const file = [{
|
|
path: '/my/test/' + filename,
|
|
name: filename
|
|
}];
|
|
|
|
const storeSpy = sinon.spy(store, 'getUniqueFileName');
|
|
const storageSpy = sinon.spy(storage, 'getStorage');
|
|
|
|
ImageHandler.loadFile(_.clone(file)).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storeSpy.calledOnce.should.be.true();
|
|
storeSpy.firstCall.args[0].originalPath.should.equal('photos/my-cat.jpeg');
|
|
storeSpy.firstCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images(\/|\\)photos$/);
|
|
storeSpy.firstCall.args[0].newPath.should.eql('/content/images/photos/my-cat.jpeg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can load a single file, removing ghost dirs', function (done) {
|
|
const filename = 'content/images/my-cat.jpeg';
|
|
|
|
const file = [{
|
|
path: '/my/test/content/images/' + filename,
|
|
name: filename
|
|
}];
|
|
|
|
const storeSpy = sinon.spy(store, 'getUniqueFileName');
|
|
const storageSpy = sinon.spy(storage, 'getStorage');
|
|
|
|
ImageHandler.loadFile(_.clone(file)).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storeSpy.calledOnce.should.be.true();
|
|
storeSpy.firstCall.args[0].originalPath.should.equal('content/images/my-cat.jpeg');
|
|
storeSpy.firstCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images$/);
|
|
storeSpy.firstCall.args[0].newPath.should.eql('/content/images/my-cat.jpeg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can load a file (subdirectory)', function (done) {
|
|
ImageHandler.__set__('urlUtils', urlUtils.getInstance({url: 'http://localhost:65535/subdir'}));
|
|
|
|
const filename = 'test-image.jpeg';
|
|
|
|
const file = [{
|
|
path: '/my/test/' + filename,
|
|
name: filename
|
|
}];
|
|
|
|
const storeSpy = sinon.spy(store, 'getUniqueFileName');
|
|
const storageSpy = sinon.spy(storage, 'getStorage');
|
|
|
|
ImageHandler.loadFile(_.clone(file)).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storeSpy.calledOnce.should.be.true();
|
|
storeSpy.firstCall.args[0].originalPath.should.equal('test-image.jpeg');
|
|
storeSpy.firstCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images$/);
|
|
storeSpy.firstCall.args[0].newPath.should.eql('/subdir/content/images/test-image.jpeg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can load multiple files', function (done) {
|
|
const 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'
|
|
}];
|
|
|
|
const storeSpy = sinon.spy(store, 'getUniqueFileName');
|
|
const storageSpy = sinon.spy(storage, 'getStorage');
|
|
|
|
ImageHandler.loadFile(_.clone(files)).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storeSpy.callCount.should.eql(4);
|
|
storeSpy.firstCall.args[0].originalPath.should.equal('testing.png');
|
|
storeSpy.firstCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images$/);
|
|
storeSpy.firstCall.args[0].newPath.should.eql('/content/images/testing.png');
|
|
storeSpy.secondCall.args[0].originalPath.should.equal('photo/kitten.jpg');
|
|
storeSpy.secondCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images(\/|\\)photo$/);
|
|
storeSpy.secondCall.args[0].newPath.should.eql('/content/images/photo/kitten.jpg');
|
|
storeSpy.thirdCall.args[0].originalPath.should.equal('content/images/animated/bunny.gif');
|
|
storeSpy.thirdCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images(\/|\\)animated$/);
|
|
storeSpy.thirdCall.args[0].newPath.should.eql('/content/images/animated/bunny.gif');
|
|
storeSpy.lastCall.args[0].originalPath.should.equal('images/puppy.jpg');
|
|
storeSpy.lastCall.args[0].targetDir.should.match(/(\/|\\)content(\/|\\)images$/);
|
|
storeSpy.lastCall.args[0].newPath.should.eql('/content/images/puppy.jpg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('MarkdownHandler', function () {
|
|
it('has the correct interface', function () {
|
|
MarkdownHandler.type.should.eql('data');
|
|
MarkdownHandler.extensions.should.be.instanceof(Array).and.have.lengthOf(2);
|
|
MarkdownHandler.extensions.should.containEql('.md');
|
|
MarkdownHandler.extensions.should.containEql('.markdown');
|
|
MarkdownHandler.contentTypes.should.be.instanceof(Array).and.have.lengthOf(2);
|
|
MarkdownHandler.contentTypes.should.containEql('application/octet-stream');
|
|
MarkdownHandler.contentTypes.should.containEql('text/plain');
|
|
MarkdownHandler.loadFile.should.be.instanceof(Function);
|
|
});
|
|
|
|
it('does convert a markdown file into a post object', function (done) {
|
|
const filename = 'draft-2014-12-19-test-1.md';
|
|
|
|
const file = [{
|
|
path: testUtils.fixtures.getImportFixturePath(filename),
|
|
name: filename
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(file).then(function (result) {
|
|
result.data.posts[0].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[0].status.should.eql('draft');
|
|
result.data.posts[0].slug.should.eql('test-1');
|
|
result.data.posts[0].title.should.eql('test-1');
|
|
result.data.posts[0].created_at.should.eql(1418990400000);
|
|
moment.utc(result.data.posts[0].created_at).format('DD MM YY HH:mm').should.eql('19 12 14 12:00');
|
|
result.data.posts[0].should.not.have.property('image');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can parse a title from a markdown file', function (done) {
|
|
const filename = 'draft-2014-12-19-test-2.md';
|
|
|
|
const file = [{
|
|
path: testUtils.fixtures.getImportFixturePath(filename),
|
|
name: filename
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(file).then(function (result) {
|
|
result.data.posts[0].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[0].status.should.eql('draft');
|
|
result.data.posts[0].slug.should.eql('test-2');
|
|
result.data.posts[0].title.should.eql('Welcome to Ghost');
|
|
result.data.posts[0].created_at.should.eql(1418990400000);
|
|
result.data.posts[0].should.not.have.property('image');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can parse a featured image from a markdown file if there is a title', function (done) {
|
|
const filename = 'draft-2014-12-19-test-3.md';
|
|
|
|
const file = [{
|
|
path: testUtils.fixtures.getImportFixturePath(filename),
|
|
name: filename
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(file).then(function (result) {
|
|
result.data.posts[0].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[0].status.should.eql('draft');
|
|
result.data.posts[0].slug.should.eql('test-3');
|
|
result.data.posts[0].title.should.eql('Welcome to Ghost');
|
|
result.data.posts[0].created_at.should.eql(1418990400000);
|
|
result.data.posts[0].image.should.eql('/images/kitten.jpg');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can import a published post', function (done) {
|
|
const filename = 'published-2014-12-19-test-1.md';
|
|
|
|
const file = [{
|
|
path: testUtils.fixtures.getImportFixturePath(filename),
|
|
name: filename
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(file).then(function (result) {
|
|
result.data.posts[0].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[0].status.should.eql('published');
|
|
result.data.posts[0].slug.should.eql('test-1');
|
|
result.data.posts[0].title.should.eql('Welcome to Ghost');
|
|
result.data.posts[0].published_at.should.eql(1418990400000);
|
|
moment.utc(result.data.posts[0].published_at).format('DD MM YY HH:mm').should.eql('19 12 14 12:00');
|
|
result.data.posts[0].should.not.have.property('image');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('does not import deleted posts', function (done) {
|
|
const filename = 'deleted-2014-12-19-test-1.md';
|
|
|
|
const file = [{
|
|
path: testUtils.fixtures.getImportFixturePath(filename),
|
|
name: filename
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(file).then(function (result) {
|
|
result.data.posts.should.be.empty();
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can import multiple files', function (done) {
|
|
const files = [{
|
|
path: testUtils.fixtures.getImportFixturePath('deleted-2014-12-19-test-1.md'),
|
|
name: 'deleted-2014-12-19-test-1.md'
|
|
}, {
|
|
path: testUtils.fixtures.getImportFixturePath('published-2014-12-19-test-1.md'),
|
|
name: 'published-2014-12-19-test-1.md'
|
|
}, {
|
|
path: testUtils.fixtures.getImportFixturePath('draft-2014-12-19-test-3.md'),
|
|
name: 'draft-2014-12-19-test-3.md'
|
|
}];
|
|
|
|
MarkdownHandler.loadFile(files).then(function (result) {
|
|
// deleted-2014-12-19-test-1.md
|
|
// doesn't get imported ;)
|
|
|
|
// loadFile doesn't guarantee order of results
|
|
const one = result.data.posts[0].status === 'published' ? 0 : 1;
|
|
|
|
const two = one === 0 ? 1 : 0;
|
|
|
|
// published-2014-12-19-test-1.md
|
|
result.data.posts[one].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[one].status.should.eql('published');
|
|
result.data.posts[one].slug.should.eql('test-1');
|
|
result.data.posts[one].title.should.eql('Welcome to Ghost');
|
|
result.data.posts[one].published_at.should.eql(1418990400000);
|
|
moment.utc(result.data.posts[one].published_at).format('DD MM YY HH:mm').should.eql('19 12 14 12:00');
|
|
result.data.posts[one].should.not.have.property('image');
|
|
|
|
// draft-2014-12-19-test-3.md
|
|
result.data.posts[two].markdown.should.eql('You\'re live! Nice.');
|
|
result.data.posts[two].status.should.eql('draft');
|
|
result.data.posts[two].slug.should.eql('test-3');
|
|
result.data.posts[two].title.should.eql('Welcome to Ghost');
|
|
result.data.posts[two].created_at.should.eql(1418990400000);
|
|
result.data.posts[two].image.should.eql('/images/kitten.jpg');
|
|
|
|
done();
|
|
}).catch(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);
|
|
});
|
|
|
|
it('does preprocess posts, users and tags correctly', function () {
|
|
const inputData = require('../../../utils/fixtures/import/import-data-1.json');
|
|
const outputData = DataImporter.preProcess(_.cloneDeep(inputData));
|
|
|
|
// Data preprocess is a noop
|
|
inputData.data.data.posts[0].should.eql(outputData.data.data.posts[0]);
|
|
inputData.data.data.tags[0].should.eql(outputData.data.data.tags[0]);
|
|
inputData.data.data.users[0].should.eql(outputData.data.data.users[0]);
|
|
});
|
|
});
|
|
|
|
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, users and tags correctly', function () {
|
|
let inputData = require('../../../utils/fixtures/import/import-data-1.json');
|
|
let outputData = ImageImporter.preProcess(_.cloneDeep(inputData));
|
|
|
|
inputData = inputData.data.data;
|
|
outputData = outputData.data.data;
|
|
|
|
inputData.posts[0].markdown.should.not.containEql('/content/images/my-image.png');
|
|
inputData.posts[0].html.should.not.containEql('/content/images/my-image.png');
|
|
outputData.posts[0].markdown.should.containEql('/content/images/my-image.png');
|
|
outputData.posts[0].html.should.containEql('/content/images/my-image.png');
|
|
|
|
inputData.posts[0].markdown.should.not.containEql('/content/images/photos/cat.jpg');
|
|
inputData.posts[0].html.should.not.containEql('/content/images/photos/cat.jpg');
|
|
outputData.posts[0].markdown.should.containEql('/content/images/photos/cat.jpg');
|
|
outputData.posts[0].html.should.containEql('/content/images/photos/cat.jpg');
|
|
|
|
inputData.posts[0].feature_image.should.eql('/images/my-image.png');
|
|
outputData.posts[0].feature_image.should.eql('/content/images/my-image.png');
|
|
|
|
inputData.tags[0].feature_image.should.eql('/images/my-image.png');
|
|
outputData.tags[0].feature_image.should.eql('/content/images/my-image.png');
|
|
|
|
inputData.users[0].profile_image.should.eql('/images/my-image.png');
|
|
inputData.users[0].cover_image.should.eql('/images/photos/cat.jpg');
|
|
outputData.users[0].profile_image.should.eql('/content/images/my-image.png');
|
|
outputData.users[0].cover_image.should.eql('/content/images/photos/cat.jpg');
|
|
});
|
|
|
|
it('does import the images correctly', function () {
|
|
const inputData = require('../../../utils/fixtures/import/import-data-1.json');
|
|
|
|
const storageApi = {
|
|
save: sinon.stub().returns(Promise.resolve())
|
|
};
|
|
|
|
const storageSpy = sinon.stub(storage, 'getStorage').callsFake(function () {
|
|
return storageApi;
|
|
});
|
|
|
|
ImageImporter.doImport(inputData.images).then(function () {
|
|
storageSpy.calledOnce.should.be.true();
|
|
storageApi.save.calledTwice.should.be.true();
|
|
});
|
|
});
|
|
});
|
|
});
|