Speed up image-size utility functions (#10784)

no issue

- add `probe-image-size` dependency
- use `probe-image-size` to fetch partial image data over the network where possible
This commit is contained in:
Kevin Ansfield 2019-06-11 16:25:15 +01:00 committed by GitHub
parent c5cb7a6155
commit bbae006eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 338 additions and 324 deletions

View File

@ -1,5 +1,6 @@
const debug = require('ghost-ignition').debug('utils:image-size');
const sizeOf = require('image-size');
const probeSizeOf = require('probe-image-size');
const url = require('url');
const Promise = require('bluebird');
const _ = require('lodash');
@ -9,50 +10,96 @@ const common = require('../common');
const config = require('../../config');
const storage = require('../../adapters/storage');
const storageUtils = require('../../adapters/storage/utils');
let getImageSizeFromUrl;
let getImageSizeFromStoragePath;
let getImageSizeFromPath;
const validator = require('../../data/validation').validator;
/**
* @description processes the Buffer result of an image file
* @param {Object} options
* @returns {Object} dimensions
*/
function fetchDimensionsFromBuffer(options) {
const buffer = options.buffer;
const imagePath = options.imagePath;
const imageObject = {};
let dimensions;
// these are formats supported by image-size but not probe-image-size
const FETCH_ONLY_FORMATS = [
'cur', 'icns', 'ico', 'dds'
];
imageObject.url = imagePath;
const REQUEST_OPTIONS = {
// we need the user-agent, otherwise some https request may fail (e.g. cloudfare)
headers: {
'User-Agent': 'Mozilla/5.0 Safari/537.36'
},
timeout: config.get('times:getImageSizeTimeoutInMS') || 10000,
retry: 0, // for `got`, used with image-size
encoding: null
};
try {
// Using the Buffer rather than an URL requires to use sizeOf synchronously.
// See https://github.com/image-size/image-size#asynchronous
dimensions = sizeOf(buffer);
// processes the Buffer result of an image file using image-size
// returns promise which resolves dimensions
function _imageSizeFromBuffer(buffer) {
return new Promise((resolve, reject) => {
try {
const dimensions = sizeOf(buffer);
// CASE: `.ico` files might have multiple images and therefore multiple sizes.
// We return the largest size found (image-size default is the first size found)
if (dimensions.images) {
dimensions.width = _.maxBy(dimensions.images, (w) => {
return w.width;
}).width;
dimensions.height = _.maxBy(dimensions.images, (h) => {
return h.height;
}).height;
// CASE: `.ico` files might have multiple images and therefore multiple sizes.
// We return the largest size found (image-size default is the first size found)
if (dimensions.images) {
dimensions.width = _.maxBy(dimensions.images, img => img.width).width;
dimensions.height = _.maxBy(dimensions.images, img => img.height).height;
}
return resolve(dimensions);
} catch (err) {
return reject(err);
}
});
}
imageObject.width = dimensions.width;
imageObject.height = dimensions.height;
return Promise.resolve(imageObject);
} catch (err) {
// use probe-image-size to download enough of an image to get it's dimensions
// returns promise which resolves dimensions
function _probeImageSizeFromUrl(url) {
// probe-image-size uses `request` npm module which doesn't have our `got`
// override with custom URL validation so it needs duplicating here
if (_.isEmpty(url) || !validator.isURL(url)) {
return Promise.reject(new common.errors.InternalServerError({
code: 'IMAGE_SIZE',
err: err,
context: imagePath
message: 'URL empty or invalid.',
code: 'URL_MISSING_INVALID',
context: url
}));
}
return probeSizeOf(url, REQUEST_OPTIONS);
}
// download full image then use image-size to get it's dimensions
// returns promise which resolves dimensions
function _fetchImageSizeFromUrl(url) {
return request(url, REQUEST_OPTIONS).then((response) => {
return _imageSizeFromBuffer(response.body);
});
}
// wrapper for appropriate probe/fetch method for getting image dimensions from a URL
// returns promise which resolves dimensions
function _imageSizeFromUrl(imageUrl) {
return new Promise((resolve, reject) => {
let parsedUrl;
try {
parsedUrl = url.parse(imageUrl);
} catch (err) {
reject(err);
}
// check if we got an url without any protocol
if (!parsedUrl.protocol) {
// CASE: our gravatar URLs start with '//' and we need to add 'http:'
// to make the request work
imageUrl = 'http:' + imageUrl;
}
const extensionMatch = imageUrl.match(/(?:\.)([a-zA-Z]{3,4})(\?|$)/) || [];
const extension = (extensionMatch[1] || '').toLowerCase();
if (FETCH_ONLY_FORMATS.includes(extension)) {
return resolve(_fetchImageSizeFromUrl(imageUrl));
} else {
return resolve(_probeImageSizeFromUrl(imageUrl));
}
});
}
// Supported formats of https://github.com/image-size/image-size:
@ -78,11 +125,7 @@ function fetchDimensionsFromBuffer(options) {
* @param {String} imagePath as URL
* @returns {Promise<Object>} imageObject or error
*/
getImageSizeFromUrl = (imagePath) => {
let requestOptions;
let parsedUrl;
let timeout = config.get('times:getImageSizeTimeoutInMS') || 10000;
const getImageSizeFromUrl = (imagePath) => {
if (storageUtils.isLocalImage(imagePath)) {
// don't make a request for a locally stored image
return getImageSizeFromStoragePath(imagePath);
@ -93,36 +136,16 @@ getImageSizeFromUrl = (imagePath) => {
imagePath = urlService.utils.urlJoin(urlService.utils.urlFor('home', true), urlService.utils.getSubdir(), '/', imagePath);
}
parsedUrl = url.parse(imagePath);
// check if we got an url without any protocol
if (!parsedUrl.protocol) {
// CASE: our gravatar URLs start with '//' and we need to add 'http:'
// to make the request work
imagePath = 'http:' + imagePath;
}
debug('requested imagePath:', imagePath);
requestOptions = {
headers: {
'User-Agent': 'Mozilla/5.0 Safari/537.36'
},
timeout: timeout,
encoding: null
};
return request(
imagePath,
requestOptions
).then((response) => {
return _imageSizeFromUrl(imagePath).then((dimensions) => {
debug('Image fetched (URL):', imagePath);
return fetchDimensionsFromBuffer({
buffer: response.body,
// we need to return the URL that's accessible for network requests as this imagePath
// value will be used as the URL for structured data
imagePath: parsedUrl.href
});
return {
url: imagePath,
width: dimensions.width,
height: dimensions.height
};
}).catch({code: 'URL_MISSING_INVALID'}, (err) => {
return Promise.reject(new common.errors.InternalServerError({
message: err.message,
@ -176,7 +199,7 @@ getImageSizeFromUrl = (imagePath) => {
* @param {String} imagePath
* @returns {object} imageObject or error
*/
getImageSizeFromStoragePath = (imagePath) => {
const getImageSizeFromStoragePath = (imagePath) => {
let filePath;
imagePath = urlService.utils.urlFor('image', {image: imagePath}, true);
@ -188,14 +211,16 @@ getImageSizeFromStoragePath = (imagePath) => {
.read({path: filePath})
.then((buf) => {
debug('Image fetched (storage):', filePath);
return fetchDimensionsFromBuffer({
buffer: buf,
// we need to return the URL that's accessible for network requests as this imagePath
// value will be used as the URL for structured data
imagePath: imagePath
});
}).catch({code: 'ENOENT'}, (err) => {
return _imageSizeFromBuffer(buf);
})
.then((dimensions) => {
return {
url: imagePath,
width: dimensions.width,
height: dimensions.height
};
})
.catch({code: 'ENOENT'}, (err) => {
return Promise.reject(new common.errors.NotFoundError({
message: err.message,
code: 'IMAGE_SIZE_STORAGE',
@ -233,7 +258,7 @@ getImageSizeFromStoragePath = (imagePath) => {
* @returns {Promise<Object>} getImageDimensions
* @description Takes a file path and returns width and height.
*/
getImageSizeFromPath = (path) => {
const getImageSizeFromPath = (path) => {
return new Promise(function getSize(resolve, reject) {
let dimensions;

View File

@ -1,4 +1,4 @@
var should = require('should'),
const should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
nock = require('nock'),
@ -6,26 +6,34 @@ var should = require('should'),
configUtils = require('../../../utils/configUtils'),
urlService = require('../../../../server/services/url'),
common = require('../../../../server/lib/common'),
storage = require('../../../../server/adapters/storage'),
// Stuff we are testing
imageSize = rewire('../../../../server/lib/image/image-size');
storage = require('../../../../server/adapters/storage');
describe('lib/image: image size', function () {
var sizeOfStub,
result,
requestMock,
secondRequestMock,
originalStoragePath;
let imageSize;
let sizeOf;
let sizeOfSpy;
let probeSizeOf;
let probeSizeOfSpy;
let originalStoragePath;
// use a 1x1 gif in nock responses because it's really small and easy to work with
const GIF1x1 = Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64');
beforeEach(function () {
imageSize = rewire('../../../../server/lib/image/image-size');
sizeOf = imageSize.__get__('sizeOf');
sizeOfSpy = sinon.spy(sizeOf);
probeSizeOf = imageSize.__get__('probeSizeOf');
probeSizeOfSpy = sinon.spy(probeSizeOf);
originalStoragePath = storage.getStorage().storagePath;
});
afterEach(function () {
sinon.restore();
configUtils.restore();
imageSize = rewire('../../../../server/lib/image/image-size');
storage.getStorage().storagePath = originalStoragePath;
});
@ -35,281 +43,234 @@ describe('lib/image: image size', function () {
});
describe('getImageSizeFromUrl', function () {
it('[success] should return image dimensions with http request', function (done) {
var url = 'http://img.stockfresh.com/files/f/feedough/x/11/1540353_20925115.jpg',
expectedImageObject =
{
height: 50,
url: 'http://img.stockfresh.com/files/f/feedough/x/11/1540353_20925115.jpg',
width: 50
};
it('[success] should return image dimensions from probe request for probe-supported extension', function (done) {
const url = 'http://img.stockfresh.com/files/f/feedough/x/11/1540353_20925115.jpg';
const expectedImageObject = {
height: 1,
url: 'http://img.stockfresh.com/files/f/feedough/x/11/1540353_20925115.jpg',
width: 1
};
requestMock = nock('http://img.stockfresh.com')
const requestMock = nock('http://img.stockfresh.com')
.get('/files/f/feedough/x/11/1540353_20925115.jpg')
.reply(200);
.reply(200, GIF1x1);
sizeOfStub = sinon.stub();
sizeOfStub.returns({width: 50, height: 50, type: 'jpg'});
imageSize.__set__('sizeOf', sizeOfStub);
imageSize.getImageSizeFromUrl(url).then(function (res) {
probeSizeOfSpy.should.have.been.called;
sizeOfSpy.should.not.have.been.called;
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should return image dimensions with https request', function (done) {
var url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256',
expectedImageObject =
{
height: 256,
url: 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256',
width: 256
};
it('[success] should return image dimensions from fetch request for non-probe-supported extension', function (done) {
const url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.ico';
const expectedImageObject = {
height: 1,
url: 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.ico',
width: 1
};
requestMock = nock('https://static.wixstatic.com')
.get('/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
const requestMock = nock('https://static.wixstatic.com')
.get('/media/355241_d31358572a2542c5a44738ddcb59e7ea.ico')
.reply(200, GIF1x1);
sizeOfStub = sinon.stub();
sizeOfStub.returns({width: 256, height: 256, type: 'png'});
imageSize.__set__('sizeOf', sizeOfStub);
imageSize.getImageSizeFromUrl(url).then(function (res) {
sizeOfSpy.should.have.been.called;
probeSizeOfSpy.should.not.have.been.called;
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should return image dimensions when no image extension given', function (done) {
// This test is mocked, but works with this specific example.
// You can comment out the mocks and the test should still pass.
var url = 'https://www.zomato.com/logo/18163505/minilogo',
expectedImageObject =
{
height: 15,
url: 'https://www.zomato.com/logo/18163505/minilogo',
width: 104
};
const url = 'https://www.zomato.com/logo/18163505/minilogo';
const expectedImageObject = {
height: 1,
url: 'https://www.zomato.com/logo/18163505/minilogo',
width: 1
};
requestMock = nock('https://www.zomato.com')
.matchHeader('User-Agent', /Mozilla\/.*Safari\/.*/)
const requestMock = nock('https://www.zomato.com')
.get('/logo/18163505/minilogo')
.reply(200, {
body: '<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 68 00 00 00 0f 08 02 00 00 00 87 8f 1d 14 00 00 03 33 49 44 41 54 58 c3 ed 97 6b 48 93 51 18>'
});
.reply(200, GIF1x1);
sizeOfStub = sinon.stub();
sizeOfStub.returns({width: 104, height: 15, type: 'png'});
imageSize.__set__('sizeOf', sizeOfStub);
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
probeSizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should returns largest image value for .ico files', function (done) {
var url = 'https://super-website.com/media/icon.ico',
expectedImageObject =
{
height: 48,
url: 'https://super-website.com/media/icon.ico',
width: 48
};
const url = 'https://super-website.com/media/icon.ico';
const expectedImageObject = {
height: 64,
url: 'https://super-website.com/media/icon.ico',
width: 64
};
requestMock = nock('https://super-website.com')
const requestMock = nock('https://super-website.com')
.get('/media/icon.ico')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
.replyWithFile(200, path.join(__dirname, '/../../../utils/fixtures/images/favicon_multi_sizes.ico'));
sizeOfStub = sinon.stub();
sizeOfStub.returns({
width: 32,
height: 32,
type: 'ico',
images: [
{width: 48, height: 48},
{width: 32, height: 32},
{width: 16, height: 16}
]
});
imageSize.__set__('sizeOf', sizeOfStub);
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should return image dimensions asset path images', function (done) {
var url = '/assets/img/logo.png?v=d30c3d1e41',
urlForStub,
urlGetSubdirStub,
expectedImageObject =
{
height: 100,
url: 'http://myblog.com/assets/img/logo.png?v=d30c3d1e41',
width: 100
};
const url = '/assets/img/logo.png?v=d30c3d1e41';
const expectedImageObject = {
height: 1,
url: 'http://myblog.com/assets/img/logo.png?v=d30c3d1e41',
width: 1
};
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
requestMock = nock('http://myblog.com')
const requestMock = nock('http://myblog.com')
.get('/assets/img/logo.png?v=d30c3d1e41')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
.reply(200, GIF1x1);
sizeOfStub = sinon.stub();
sizeOfStub.returns({width: 100, height: 100, type: 'svg'});
imageSize.__set__('sizeOf', sizeOfStub);
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
probeSizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should return image dimensions for gravatar images request', function (done) {
var url = '//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x',
expectedImageObject =
{
height: 250,
url: '//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x',
width: 250
};
const url = '//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x';
const expectedImageObject = {
height: 1,
url: '//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x',
width: 1
};
requestMock = nock('http://www.gravatar.com')
const requestMock = nock('http://www.gravatar.com')
.get('/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
.reply(200, GIF1x1);
sizeOfStub = sinon.stub();
sizeOfStub.returns({width: 250, height: 250, type: 'jpg'});
imageSize.__set__('sizeOf', sizeOfStub);
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
probeSizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] can handle redirect', function (done) {
var url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg',
expectedImageObject =
{
height: 100,
url: 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg',
width: 100
};
it('[success] can handle redirect (probe-image-size)', function (done) {
const url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg';
const expectedImageObject = {
height: 1,
url: 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg',
width: 1
};
requestMock = nock('http://noimagehere.com')
const requestMock = nock('http://noimagehere.com')
.get('/files/f/feedough/x/11/1540353_20925115.jpg')
.reply(301, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
},
{
location: 'http://someredirectedurl.com/files/f/feedough/x/11/1540353_20925115.jpg'
});
secondRequestMock = nock('http://someredirectedurl.com')
.get('/files/f/feedough/x/11/1540353_20925115.jpg')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
.reply(301, null, {
location: 'http://someredirectedurl.com/files/f/feedough/x/11/1540353_20925115.jpg'
});
sizeOfStub.returns({width: 100, height: 100, type: 'jpg'});
imageSize.__set__('sizeOf', sizeOfStub);
const secondRequestMock = nock('http://someredirectedurl.com')
.get('/files/f/feedough/x/11/1540353_20925115.jpg')
.reply(200, GIF1x1);
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
probeSizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
secondRequestMock.isDone().should.be.true();
should.exist(res);
res.width.should.be.equal(expectedImageObject.width);
res.height.should.be.equal(expectedImageObject.height);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] can handle redirect (image-size)', function (done) {
const url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.gif';
const expectedImageObject = {
height: 1,
url: 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.gif',
width: 1
};
const requestMock = nock('http://noimagehere.com')
.get('/files/f/feedough/x/11/1540353_20925115.gif')
.reply(301, null, {
location: 'http://someredirectedurl.com/files/f/feedough/x/11/1540353_20925115.gif'
});
const secondRequestMock = nock('http://someredirectedurl.com')
.get('/files/f/feedough/x/11/1540353_20925115.gif')
.reply(200, GIF1x1);
imageSize.getImageSizeFromUrl(url).then(function (res) {
sizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
secondRequestMock.isDone().should.be.true();
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
should.exist(res.height);
res.height.should.be.equal(expectedImageObject.height);
should.exist(res.url);
res.url.should.be.equal(expectedImageObject.url);
done();
}).catch(done);
});
it('[success] should switch to local file storage if available', function (done) {
var url = '/content/images/favicon.png',
urlForStub,
urlGetSubdirStub,
expectedImageObject =
{
height: 100,
url: 'http://myblog.com/content/images/favicon.png',
width: 100
};
const url = '/content/images/favicon.png';
const expectedImageObject = {
height: 100,
url: 'http://myblog.com/content/images/favicon.png',
width: 100
};
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/content/images/favicon.png');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
requestMock = nock('http://myblog.com')
const requestMock = nock('http://myblog.com')
.get('/content/images/favicon.png')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
result = imageSize.getImageSizeFromUrl(url).then(function (res) {
imageSize.getImageSizeFromUrl(url).then(function (res) {
requestMock.isDone().should.be.false();
should.exist(res);
should.exist(res.width);
@ -322,15 +283,34 @@ describe('lib/image: image size', function () {
}).catch(done);
});
it('[failure] can handle an error with statuscode not 200', function (done) {
var url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg';
it('[failure] can handle an error with statuscode not 200 (probe-image-size)', function (done) {
const url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.jpg';
requestMock = nock('http://noimagehere.com')
const requestMock = nock('http://noimagehere.com')
.get('/files/f/feedough/x/11/1540353_20925115.jpg')
.reply(404);
result = imageSize.getImageSizeFromUrl(url)
imageSize.getImageSizeFromUrl(url)
.catch(function (err) {
probeSizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
should.exist(err);
err.errorType.should.be.equal('NotFoundError');
err.message.should.be.equal('Image not found.');
done();
});
});
it('[failure] can handle an error with statuscode not 200 (image-size)', function (done) {
const url = 'http://noimagehere.com/files/f/feedough/x/11/1540353_20925115.gif';
const requestMock = nock('http://noimagehere.com')
.get('/files/f/feedough/x/11/1540353_20925115.gif')
.reply(404);
imageSize.getImageSizeFromUrl(url)
.catch(function (err) {
sizeOfSpy.should.have.been.called;
requestMock.isDone().should.be.true();
should.exist(err);
err.errorType.should.be.equal('NotFoundError');
@ -340,9 +320,9 @@ describe('lib/image: image size', function () {
});
it('[failure] handles invalid URL', function (done) {
var url = 'Not-a-valid-url';
const url = 'Not-a-valid-url';
result = imageSize.getImageSizeFromUrl(url)
imageSize.getImageSizeFromUrl(url)
.catch(function (err) {
should.exist(err);
err.errorType.should.be.equal('InternalServerError');
@ -352,15 +332,15 @@ describe('lib/image: image size', function () {
});
it('[failure] will timeout', function (done) {
var url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256';
const url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256';
requestMock = nock('https://static.wixstatic.com')
const requestMock = nock('https://static.wixstatic.com')
.get('/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256')
.socketDelay(11)
.reply(408);
configUtils.set('times:getImageSizeTimeoutInMS', 10);
result = imageSize.getImageSizeFromUrl(url)
imageSize.getImageSizeFromUrl(url)
.catch(function (err) {
requestMock.isDone().should.be.true();
should.exist(err);
@ -370,20 +350,42 @@ describe('lib/image: image size', function () {
});
});
it('[failure] returns error if \`image-size`\ module throws error', function (done) {
var url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256';
it('[failure] returns error if \`probe-image-size`\ module throws error', function (done) {
const url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg';
requestMock = nock('https://static.wixstatic.com')
.get('/media/355241_d31358572a2542c5a44738ddcb59e7ea.jpg_256')
const probeSizeOfStub = sinon.stub();
probeSizeOfStub.throws({error: 'probe-image-size could not find dimensions'});
imageSize.__set__('probeSizeOf', probeSizeOfStub);
imageSize.getImageSizeFromUrl(url)
.then(() => {
true.should.be.false('succeeded when expecting failure');
})
.catch(function (err) {
should.exist(err);
err.errorType.should.be.equal('InternalServerError');
err.error.should.be.equal('probe-image-size could not find dimensions');
done();
});
});
it('[failure] returns error if \`image-size`\ module throws error', function (done) {
const url = 'https://static.wixstatic.com/media/355241_d31358572a2542c5a44738ddcb59e7ea.ico';
const requestMock = nock('https://static.wixstatic.com')
.get('/media/355241_d31358572a2542c5a44738ddcb59e7ea.ico')
.reply(200, {
body: '<Buffer 2c be a4 40 f7 87 73 1e 57 2c c1 e4 0d 79 03 95 42 f0 42 2e 41 95 27 c9 5c 35 a7 71 2c 09 5a 57 d3 04 1e 83 03 28 07 96 b0 c8 88 65 07 7a d1 d6 63 50>'
});
sizeOfStub = sinon.stub();
const sizeOfStub = sinon.stub();
sizeOfStub.throws({error: 'image-size could not find dimensions'});
imageSize.__set__('sizeOf', sizeOfStub);
result = imageSize.getImageSizeFromUrl(url)
imageSize.getImageSizeFromUrl(url)
.then(() => {
true.should.be.false('succeeded when expecting failure');
})
.catch(function (err) {
requestMock.isDone().should.be.true();
should.exist(err);
@ -394,13 +396,13 @@ describe('lib/image: image size', function () {
});
it('[failure] returns error if request errors', function (done) {
var url = 'https://notarealwebsite.com/images/notapicture.jpg';
const url = 'https://notarealwebsite.com/images/notapicture.jpg';
requestMock = nock('https://notarealwebsite.com')
const requestMock = nock('https://notarealwebsite.com')
.get('/images/notapicture.jpg')
.reply(500, {message: 'something awful happened', code: 'AWFUL_ERROR'});
result = imageSize.getImageSizeFromUrl(url)
imageSize.getImageSizeFromUrl(url)
.catch(function (err) {
requestMock.isDone().should.be.true();
should.exist(err);
@ -413,24 +415,21 @@ describe('lib/image: image size', function () {
describe('getImageSizeFromStoragePath', function () {
it('[success] should return image dimensions for locally stored images', function (done) {
var url = '/content/images/ghost-logo.png',
urlForStub,
urlGetSubdirStub,
expectedImageObject =
{
height: 257,
url: 'http://myblog.com/content/images/ghost-logo.png',
width: 800
};
const url = '/content/images/ghost-logo.png';
const expectedImageObject = {
height: 257,
url: 'http://myblog.com/content/images/ghost-logo.png',
width: 800
};
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/content/images/ghost-logo.png');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
result = imageSize.getImageSizeFromStoragePath(url).then(function (res) {
imageSize.getImageSizeFromStoragePath(url).then(function (res) {
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
@ -443,24 +442,21 @@ describe('lib/image: image size', function () {
});
it('[success] should return image dimensions for locally stored images with subdirectory', function (done) {
var url = '/content/images/favicon_too_large.png',
urlForStub,
urlGetSubdirStub,
expectedImageObject =
{
height: 1010,
url: 'http://myblog.com/blog/content/images/favicon_too_large.png',
width: 1010
};
const url = '/content/images/favicon_too_large.png';
const expectedImageObject = {
height: 1010,
url: 'http://myblog.com/blog/content/images/favicon_too_large.png',
width: 1010
};
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/blog/content/images/favicon_too_large.png');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('/blog');
result = imageSize.getImageSizeFromStoragePath(url).then(function (res) {
imageSize.getImageSizeFromStoragePath(url).then(function (res) {
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
@ -473,24 +469,21 @@ describe('lib/image: image size', function () {
});
it('[success] should return largest image dimensions for locally stored .ico image', function (done) {
var url = 'http://myblog.com/content/images/favicon_multi_sizes.ico',
urlForStub,
urlGetSubdirStub,
expectedImageObject =
{
height: 64,
url: 'http://myblog.com/content/images/favicon_multi_sizes.ico',
width: 64
};
const url = 'http://myblog.com/content/images/favicon_multi_sizes.ico';
const expectedImageObject = {
height: 64,
url: 'http://myblog.com/content/images/favicon_multi_sizes.ico',
width: 64
};
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/content/images/favicon_multi_sizes.ico');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
result = imageSize.getImageSizeFromStoragePath(url).then(function (res) {
imageSize.getImageSizeFromStoragePath(url).then(function (res) {
should.exist(res);
should.exist(res.width);
res.width.should.be.equal(expectedImageObject.width);
@ -503,18 +496,16 @@ describe('lib/image: image size', function () {
});
it('[failure] returns error if storage adapter errors', function (done) {
var url = '/content/images/not-existing-image.png',
urlForStub,
urlGetSubdirStub;
const url = '/content/images/not-existing-image.png';
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/content/images/not-existing-image.png');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
result = imageSize.getImageSizeFromStoragePath(url)
imageSize.getImageSizeFromStoragePath(url)
.catch(function (err) {
should.exist(err);
(err instanceof common.errors.NotFoundError).should.eql(true);
@ -523,22 +514,20 @@ describe('lib/image: image size', function () {
});
it('[failure] returns error if \`image-size`\ module throws error', function (done) {
var url = '/content/images/ghost-logo.pngx',
urlForStub,
urlGetSubdirStub;
const url = '/content/images/ghost-logo.pngx';
sizeOfStub = sinon.stub();
const sizeOfStub = sinon.stub();
sizeOfStub.throws({error: 'image-size could not find dimensions'});
imageSize.__set__('sizeOf', sizeOfStub);
storage.getStorage().storagePath = path.join(__dirname, '../../../../test/utils/fixtures/images/');
urlForStub = sinon.stub(urlService.utils, 'urlFor');
const urlForStub = sinon.stub(urlService.utils, 'urlFor');
urlForStub.withArgs('image').returns('http://myblog.com/content/images/ghost-logo.pngx');
urlForStub.withArgs('home').returns('http://myblog.com/');
urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
const urlGetSubdirStub = sinon.stub(urlService.utils, 'getSubdir');
urlGetSubdirStub.returns('');
result = imageSize.getImageSizeFromStoragePath(url)
imageSize.getImageSizeFromStoragePath(url)
.catch(function (err) {
should.exist(err);
err.error.should.be.equal('image-size could not find dimensions');

View File

@ -111,7 +111,7 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"path-match": "1.2.4",
"probe-image-size": "^4.0.0",
"probe-image-size": "4.0.0",
"rss": "1.2.2",
"sanitize-html": "1.20.0",
"semver": "5.6.0",

View File

@ -5701,7 +5701,7 @@ prettyjson@^1.1.3:
colors "^1.1.2"
minimist "^1.2.0"
probe-image-size@^4.0.0:
probe-image-size@4.0.0, probe-image-size@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-4.0.0.tgz#d35b71759e834bcf580ea9f18ec8b9265c0977eb"
integrity sha512-nm7RvWUxps+2+jZKNLkd04mNapXNariS6G5WIEVzvAqjx7EUuKcY1Dp3e6oUK7GLwzJ+3gbSbPLFAASHFQrPcQ==