mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-13 14:39:52 +03:00
ba3d4b3689
closes #4608, #4609 - image handler loads in any image files & figures out where they'll get stored - image importer has a preprocessor which replaces image paths in pertinent spots of post, tag and user models - image importer stores images, keeping the path where it makes sense - basic test for the preprocessor
426 lines
20 KiB
JavaScript
426 lines
20 KiB
JavaScript
/*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;
|
|
});
|
|
});
|
|
});
|
|
});
|