From 1606cbab18598352809e24b19743bc1d74036d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 1 Jun 2015 14:57:05 +0200 Subject: [PATCH] Add Image Optimization rule to the dashbard --- lib/metadata/policies.js | 10 + lib/metadata/scoreProfileGeneric.json | 3 +- lib/tools/weightChecker/imageOptimizer.js | 211 ++++++++++++---------- lib/tools/weightChecker/weightChecker.js | 113 +++++++++--- test/core/imageOptimizerTest.js | 58 +++++- test/core/weightCheckerTest.js | 89 ++++++++- test/{fixtures => www}/jpeg-image.jpg | Bin test/www/logo-large.png | Bin 0 -> 4193 bytes test/{fixtures => www}/png-image.png | Bin test/{fixtures => www}/svg-image.svg | 0 10 files changed, 351 insertions(+), 133 deletions(-) rename test/{fixtures => www}/jpeg-image.jpg (100%) create mode 100644 test/www/logo-large.png rename test/{fixtures => www}/png-image.png (100%) rename test/{fixtures => www}/svg-image.svg (100%) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 376ce7f..0701acb 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -862,6 +862,16 @@ var policies = { "hasOffenders": true, "unit": 'bytes' }, + "imageOptimization": { + "tool": "weightChecker", + "label": "Image optimization", + "message": "

This metric mesures the number of bytes that could be saved by optimizing images.

Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they lie to you because they're generally not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOption on Mac.

The tools in use in YellowLabTools are not set to their maximum optimization power, so you might be able to compress even more (the max JPEG quality is set to 85, which should be sufficient for any website).

", + "isOkThreshold": 30720, + "isBadThreshold": 122880, + "isAbnormalThreshold": 204800, + "hasOffenders": true, + "unit": 'bytes' + }, "requests": { "tool": "phantomas", "label": "Total requests number", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 250a178..81c7781 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -72,7 +72,8 @@ "pageWeight": { "label": "Page weight", "policies": { - "totalWeight": 5 + "totalWeight": 5, + "imageOptimization": 3 } }, "requests": { diff --git a/lib/tools/weightChecker/imageOptimizer.js b/lib/tools/weightChecker/imageOptimizer.js index a14c434..be0944c 100644 --- a/lib/tools/weightChecker/imageOptimizer.js +++ b/lib/tools/weightChecker/imageOptimizer.js @@ -7,25 +7,38 @@ var jpegoptim = require('imagemin-jpegoptim'); var ImageOptimizer = function() { var MAX_JPEG_QUALITY = 85; + var OPTIPNG_COMPRESSION_LEVEL = 2; function optimizeImage(entry) { var deferred = Q.defer(); - var fileSize = entry.weightCheck.body.length; + if (!entry.weightCheck.body) { + // No valid file available + deferred.resolve(entry); + return deferred.promise; + } + + var fileSize = entry.weightCheck.bodySize; + debug('Let\'s try to optimize %s', entry.url); debug('Current file size is %d', fileSize); if (isJpeg(entry)) { debug('File is a JPEG'); // Starting softly with a lossless compression - return compressJpegLosslessly(entry.weightCheck.body) + return compressJpegLosslessly(new Buffer(entry.weightCheck.body, 'binary')) .then(function(newFile) { + if (!newFile) { + debug('Optimization didn\'t work'); + return entry; + } + var newFileSize = newFile.contents.length; debug('JPEG lossless compression complete for %s', entry.url); - if (newFileSize < fileSize) { + if (gainIsEnough(fileSize, newFileSize)) { entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize; entry.weightCheck.isOptimized = false; debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); @@ -33,24 +46,34 @@ var ImageOptimizer = function() { // Now let's compress lossy to MAX_JPEG_QUALITY - return compressJpegLossly(entry.weightCheck.body); + return compressJpegLossly(new Buffer(entry.weightCheck.body, 'binary')); }) .then(function(newFile) { + if (!newFile) { + debug('Optimization didn\'t work'); + return entry; + } + var newFileSize = newFile.contents.length; debug('JPEG lossy compression complete for %s', entry.url); - if (newFileSize < fileSize) { + if (gainIsEnough(fileSize, newFileSize)) { + + if (entry.weightCheck.isOptimized !== false || newFileSize < entry.weightCheck.lossless) { + entry.weightCheck.optimized = newFileSize; + } + entry.weightCheck.lossy = newFileSize; entry.weightCheck.isOptimized = false; debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); - - if (newFileSize < entry.weightCheck.lossless) { - entry.weightCheck.optimized = newFileSize; - } } + return entry; + }) + + .fail(function() { return entry; }); @@ -59,19 +82,32 @@ var ImageOptimizer = function() { debug('File is a PNG'); // Starting softly with a lossless compression - return compressPngLosslessly(entry.weightCheck.body) + return compressPngLosslessly(new Buffer(entry.weightCheck.body, 'binary')) .then(function(newFile) { + if (!newFile) { + debug('Optimization didn\'t work'); + return entry; + } + var newFileSize = newFile.contents.length; debug('PNG lossless compression complete for %s', entry.url); - if (newFileSize < fileSize) { + debug('Old file size: %d', fileSize); + debug('New file size: %d', newFileSize); + debug('newgainIsEnough: %s', gainIsEnough(fileSize, newFileSize) ? 'true':'false'); + + if (gainIsEnough(fileSize, newFileSize)) { entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize; entry.weightCheck.isOptimized = false; debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); } + return entry; + }) + + .fail(function() { return entry; }); @@ -80,137 +116,122 @@ var ImageOptimizer = function() { debug('File is an SVG'); // Starting softly with a lossless compression - return compressSvgLosslessly(entry.weightCheck.body) + return compressSvgLosslessly(new Buffer(entry.weightCheck.body, 'utf8')) .then(function(newFile) { + if (!newFile) { + debug('Optimization didn\'t work'); + return entry; + } + var newFileSize = newFile.contents.length; debug('SVG lossless compression complete for %s', entry.url); - if (newFileSize < fileSize) { + if (gainIsEnough(fileSize, newFileSize)) { entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize; entry.weightCheck.isOptimized = false; debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); } + return entry; + }) + + .fail(function() { return entry; }); } else { - debug('File type is not an optimizable image'); + debug('File type %s is not an (optimizable) image', entry.contentType); deferred.resolve(entry); } return deferred.promise; } + function gainIsEnough(oldWeight, newWeight) { + var gain = oldWeight - newWeight; + var ratio = gain / oldWeight; + return (gain > 2048 || ratio > 0.2); + } + function isJpeg(entry) { return entry.isImage && entry.contentType === 'image/jpeg'; } - function compressJpegLosslessly(imageBody) { - var deferred = Q.defer(); - var startTime = Date.now(); - - debug('Starting JPEG lossless compression'); - - new Imagemin() - .src(imageBody) - .use(Imagemin.jpegtran()) - .run(function (err, files) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(files[0]); - var endTime = Date.now(); - debug('compressJpegLosslessly took %d ms', endTime - startTime); - } - }); - - return deferred.promise; - } - - function compressJpegLossly(imageBody) { - var deferred = Q.defer(); - var startTime = Date.now(); - - debug('Starting JPEG lossy compression'); - - new Imagemin() - .src(imageBody) - .use(jpegoptim({max: MAX_JPEG_QUALITY})) - .run(function (err, files) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(files[0]); - var endTime = Date.now(); - debug('compressJpegLossly took %d ms', endTime - startTime); - } - }); - - return deferred.promise; - } - function isPNG(entry) { return entry.isImage && entry.contentType === 'image/png'; } - function compressPngLosslessly(imageBody) { - var deferred = Q.defer(); - var startTime = Date.now(); - - debug('Starting PNG losslessly compression'); - - new Imagemin() - .src(imageBody) - .use(Imagemin.optipng({optimizationLevel: 2})) - .run(function (err, files) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(files[0]); - var endTime = Date.now(); - debug('compressPngLosslessly took %d ms', endTime - startTime); - } - }); - - return deferred.promise; - } - function isSVG(entry) { return entry.isImage && entry.contentType === 'image/svg+xml'; } + function compressJpegLosslessly(imageBody) { + return imageminLauncher(imageBody, 'jpeg', false); + } + + function compressJpegLossly(imageBody) { + return imageminLauncher(imageBody, 'jpeg', true); + } + + function compressPngLosslessly(imageBody) { + return imageminLauncher(imageBody, 'png', false); + } + function compressSvgLosslessly(imageBody) { + return imageminLauncher(imageBody, 'svg', false); + } + + function imageminLauncher(imageBody, type, lossy) { var deferred = Q.defer(); var startTime = Date.now(); - debug('Starting SVG losslessly compression'); + debug('Starting %s %s optimization', type, lossy ? 'lossy' : 'lossless'); - new Imagemin() - .src(imageBody) - .use(Imagemin.svgo()) - .run(function (err, files) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(files[0]); - var endTime = Date.now(); - debug('compressSvgLosslessly took %d ms', endTime - startTime); - } - }); + var engine; + if (type === 'jpeg' && !lossy) { + engine = Imagemin.jpegtran(); + } else if (type === 'jpeg' && lossy) { + engine = jpegoptim({max: MAX_JPEG_QUALITY}); + } else if (type === 'png' && !lossy) { + engine = Imagemin.optipng({optimizationLevel: OPTIPNG_COMPRESSION_LEVEL}); + } else if (type === 'svg' && !lossy) { + engine = Imagemin.svgo(); + } else { + deferred.reject('No optimization engine found for imagemin'); + } + + try { + + new Imagemin() + .src(imageBody) + .use(engine) + .run(function (err, files) { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(files[0]); + var endTime = Date.now(); + debug('Optimization for %s took %d ms', type, endTime - startTime); + } + }); + + } catch(err) { + deferred.reject(err); + } return deferred.promise; } return { - //recompressIfImage: recompressIfImage, + recompressIfImage: optimizeImage, optimizeImage: optimizeImage, compressJpegLosslessly: compressJpegLosslessly, compressJpegLossly: compressJpegLossly, compressPngLosslessly: compressPngLosslessly, - compressSvgLosslessly: compressSvgLosslessly + compressSvgLosslessly: compressSvgLosslessly, + gainIsEnough: gainIsEnough }; }; diff --git a/lib/tools/weightChecker/weightChecker.js b/lib/tools/weightChecker/weightChecker.js index 75e1e5b..8c2fe5a 100644 --- a/lib/tools/weightChecker/weightChecker.js +++ b/lib/tools/weightChecker/weightChecker.js @@ -5,18 +5,23 @@ */ -var debug = require('debug')('ylt:weightChecker'); -var Q = require('q'); -var http = require('http'); -var zlib = require('zlib'); -var async = require('async'); -var request = require('request'); +var debug = require('debug')('ylt:weightChecker'); +var Q = require('q'); +var http = require('http'); +var zlib = require('zlib'); +var async = require('async'); +var request = require('request'); + +var imageOptimizer = require('./imageOptimizer'); + var WeightChecker = function() { var MAX_PARALLEL_DOWNLOADS = 10; var REQUEST_TIMEOUT = 10000; // 10 seconds + + // This function will re-download every asset and check if it could be optimized function recheckAllFiles(data) { var deferred = Q.defer(); @@ -30,10 +35,10 @@ var WeightChecker = function() { redownloadEntry(entry) - .then(recompressIfImage) + .then(imageOptimizer.recompressIfImage) - .then(function(entry) { - callback(null, entry); + .then(function(newEntry) { + callback(null, newEntry); }) .fail(function(err) { @@ -58,6 +63,10 @@ var WeightChecker = function() { offenders.totalWeight = listRequestWeight(results); metrics.totalWeight = offenders.totalWeight.totalWeight; + // Image compression + offenders.imageOptimization = listImageNotOptimized(results); + metrics.imageOptimization = offenders.imageOptimization.totalGain; + data.toolsResults.weightChecker = { metrics: metrics, @@ -128,6 +137,62 @@ var WeightChecker = function() { } + function listImageNotOptimized(requests) { + var results = { + totalGain: 0, + byType: { + jpeg: { + totalGain: 0, + requests: [] + }, + png: { + totalGain: 0, + requests: [] + }, + svg: { + totalGain: 0, + requests: [] + } + } + }; + + requests.forEach(function(req) { + var type = null; + + switch(req.contentType) { + case 'image/jpeg': + type = 'jpeg'; + break; + case 'image/png': + type = 'png'; + break; + case 'image/svg+xml': + type = 'svg'; + break; + } + + if (type && req.weightCheck.bodySize && req.weightCheck.isOptimized === false) { + var gain = req.weightCheck.bodySize - req.weightCheck.optimized; + + results.totalGain += gain; + + results.byType[type].totalGain += gain; + + results.byType[type].requests.push({ + url: req.url, + original: req.weightCheck.bodySize, + optimized: req.weightCheck.optimized, + lossless: req.weightCheck.lossless, + lossy: req.weightCheck.lossy, + gain: gain + }); + } + }); + + return results; + } + + function redownloadEntry(entry) { var deferred = Q.defer(); @@ -136,7 +201,7 @@ var WeightChecker = function() { entry.weightCheck = { message: message }; - deferred.reject(); + deferred.resolve(entry); } if (entry.method !== 'GET') { @@ -161,9 +226,10 @@ var WeightChecker = function() { url: entry.url, headers: reqHeaders, timeout: REQUEST_TIMEOUT + //encoding: (entry.contentType === 'image/jpeg' || entry.contentType === 'image/png') ? 'binary' : null }; - download(requestOptions, function(error, result) { + download(requestOptions, entry.contentType, function(error, result) { if (error) { if (error.code === 'ETIMEDOUT') { onError('timeout after ' + REQUEST_TIMEOUT + 'ms'); @@ -183,7 +249,7 @@ var WeightChecker = function() { } // Inspired by https://github.com/cvan/fastHAR-api/blob/10cec585/app.js - function download(requestOptions, callback) { + function download(requestOptions, contentType, callback) { var statusCode; @@ -226,6 +292,7 @@ var WeightChecker = function() { switch (res.headers['content-encoding']) { case 'gzip': + var gzip = zlib.createGunzip(); gzip.on('data', function (data) { @@ -234,6 +301,8 @@ var WeightChecker = function() { }).on('end', function () { isCompressed = true; tally(); + }).on('error', function(err) { + debug(err); }); res.on('data', function (data) { @@ -242,6 +311,8 @@ var WeightChecker = function() { break; case 'deflate': + res.setEncoding('utf8'); + var deflate = zlib.createInflate(); deflate.on('data', function (data) { @@ -250,6 +321,8 @@ var WeightChecker = function() { }).on('end', function () { isCompressed = true; tally(); + }).on('error', function(err) { + debug(err); }); res.on('data', function (data) { @@ -258,6 +331,10 @@ var WeightChecker = function() { break; default: + if (contentType === 'image/jpeg' || contentType === 'image/png') { + res.setEncoding('binary'); + } + res.on('data', function (data) { body += data; uncompressedSize += data.length; @@ -280,21 +357,11 @@ var WeightChecker = function() { } - function recompressIfImage(entry) { - var deferred = Q.defer(); - - deferred.resolve(entry); - - return deferred.promise; - } - - return { recheckAllFiles: recheckAllFiles, listRequestWeight: listRequestWeight, redownloadEntry: redownloadEntry, - download: download, - recompressIfImage: recompressIfImage + download: download }; }; diff --git a/test/core/imageOptimizerTest.js b/test/core/imageOptimizerTest.js index 4a26213..07df3c5 100644 --- a/test/core/imageOptimizerTest.js +++ b/test/core/imageOptimizerTest.js @@ -6,7 +6,7 @@ var path = require('path'); describe('imageOptimizer', function() { it('should optimize a JPEG image losslessly', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/jpeg-image.jpg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg')); var fileSize = fileContent.length; @@ -20,7 +20,7 @@ describe('imageOptimizer', function() { }); it('should optimize a JPEG image lossly', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/jpeg-image.jpg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg')); var fileSize = fileContent.length; @@ -34,7 +34,7 @@ describe('imageOptimizer', function() { }); it('should find the best optimization for a jpeg', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/jpeg-image.jpg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg')); var fileSize = fileContent.length; var entry = { @@ -77,7 +77,7 @@ describe('imageOptimizer', function() { }); it('should optimize a PNG image losslessly', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/png-image.png')); var fileSize = fileContent.length; @@ -90,8 +90,36 @@ describe('imageOptimizer', function() { }); }); + it('should fail to optimize an already optimized PNG', function(done) { + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/logo-large.png')); + + var fileSize = fileContent.length; + + imageOptimizer.compressPngLosslessly(fileContent).then(function(newFile) { + var newFileSize = newFile.contents.length; + newFileSize.should.equal(fileSize); + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should fail to optimize a non-PNG', function(done) { + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/svg-image.svg')); + + var fileSize = fileContent.length; + + imageOptimizer.compressPngLosslessly(fileContent).then(function(newFile) { + var newFileSize = newFile.contents.length; + newFileSize.should.equal(fileSize); + done(); + }).fail(function(err) { + done(err); + }); + }); + it('should optimize a png', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/png-image.png')); var fileSize = fileContent.length; var entry = { @@ -132,9 +160,8 @@ describe('imageOptimizer', function() { }); }); - it('should optimize an SVG image losslessly', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/svg-image.svg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/svg-image.svg')); var fileSize = fileContent.length; @@ -148,7 +175,7 @@ describe('imageOptimizer', function() { }); it('should optimize an SVG', function(done) { - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/svg-image.svg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/svg-image.svg')); var fileSize = fileContent.length; var entry = { @@ -194,7 +221,7 @@ describe('imageOptimizer', function() { // In this test, we try to optimize a PNG but with a falsy "image/jpeg" content type - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/png-image.png')); var fileSize = fileContent.length; var entry = { @@ -237,7 +264,7 @@ describe('imageOptimizer', function() { // In this test, we try to optimize a JPEG but with a falsy "image/png" content type - var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/jpeg-image.jpg')); + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg')); var fileSize = fileContent.length; var entry = { @@ -276,4 +303,15 @@ describe('imageOptimizer', function() { }); }); + it('should determine if gain is enough', function() { + imageOptimizer.gainIsEnough(20000, 10000).should.equal(true); + imageOptimizer.gainIsEnough(2000, 1000).should.equal(true); + imageOptimizer.gainIsEnough(20000, 21000).should.equal(false); + imageOptimizer.gainIsEnough(20000, 40000).should.equal(false); + imageOptimizer.gainIsEnough(20000, 19500).should.equal(false); + imageOptimizer.gainIsEnough(200, 100).should.equal(true); + imageOptimizer.gainIsEnough(2000, 1900).should.equal(false); + imageOptimizer.gainIsEnough(200000, 197000).should.equal(true); + }); + }); diff --git a/test/core/weightCheckerTest.js b/test/core/weightCheckerTest.js index 01b0ff7..eb04860 100644 --- a/test/core/weightCheckerTest.js +++ b/test/core/weightCheckerTest.js @@ -1,5 +1,7 @@ var should = require('chai').should(); var weightChecker = require('../../lib/tools/weightChecker/weightChecker'); +var fs = require('fs'); +var path = require('path'); describe('weightChecker', function() { @@ -14,7 +16,8 @@ describe('weightChecker', function() { Accept: '*/*' }, status: 200, - isHTML: true + isHTML: true, + type: 'html' }, { method: 'GET', @@ -25,7 +28,34 @@ describe('weightChecker', function() { Accept: '*/*' }, status: 200, - isJS: true + isJS: true, + type: 'js' + }, + { + method: 'GET', + url: 'http://localhost:8388/jpeg-image.jpg', + requestHeaders: { + 'User-Agent': 'something', + Referer: 'http://www.google.fr/', + Accept: '*/*' + }, + status: 200, + isImage: true, + type: 'image', + contentType: 'image/jpeg' + }, + { + method: 'GET', + url: 'http://localhost:8388/svg-image.svg', + requestHeaders: { + 'User-Agent': 'something', + Referer: 'http://www.google.fr/', + Accept: '*/*' + }, + status: 200, + isImage: true, + type: 'image', + contentType: 'image/svg+xml' } ]; @@ -48,6 +78,18 @@ describe('weightChecker', function() { data.toolsResults.should.have.a.property('weightChecker'); data.toolsResults.weightChecker.should.have.a.property('metrics'); data.toolsResults.weightChecker.should.have.a.property('offenders'); + + data.toolsResults.weightChecker.offenders.should.have.a.property('totalWeight'); + data.toolsResults.weightChecker.offenders.totalWeight.totalWeight.should.be.above(0); + data.toolsResults.weightChecker.offenders.totalWeight.byType.html.requests.length.should.equal(1); + data.toolsResults.weightChecker.offenders.totalWeight.byType.js.requests.length.should.equal(1); + data.toolsResults.weightChecker.offenders.totalWeight.byType.image.requests.length.should.equal(2); + + data.toolsResults.weightChecker.offenders.should.have.a.property('imageOptimization'); + data.toolsResults.weightChecker.offenders.imageOptimization.totalGain.should.be.above(0); + data.toolsResults.weightChecker.offenders.imageOptimization.byType.jpeg.requests.length.should.equal(1); + data.toolsResults.weightChecker.offenders.imageOptimization.byType.svg.requests.length.should.equal(1); + done(); }) @@ -77,7 +119,7 @@ describe('weightChecker', function() { newEntry.weightCheck.uncompressedSize.should.equal(newEntry.weightCheck.bodySize); newEntry.weightCheck.isCompressed.should.equal(false); newEntry.weightCheck.headersSize.should.be.above(200).and.below(400); - newEntry.weightCheck.body.should.have.string('1.8.3'); + newEntry.weightCheck.body.toString().should.have.string('1.8.3'); done(); }) @@ -87,6 +129,45 @@ describe('weightChecker', function() { }); }); + it('should download a PNG image and find the same body as fs.readFile', function(done) { + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/logo-large.png')); + + var entry = { + method: 'GET', + url: 'http://localhost:8388/logo-large.png', + requestHeaders: { + 'User-Agent': 'something', + Referer: 'http://www.google.fr/', + Accept: '*/*' + }, + status: 200, + isImage: true, + contentType: 'image/png' + }; + + weightChecker.redownloadEntry(entry) + + .then(function(newEntry) { + + newEntry.weightCheck.bodySize.should.equal(4193); + newEntry.weightCheck.body.should.equal(fileContent.toString('binary')); + + // Opening the image in lwip to check if the format is good + var lwip = require('lwip'); + var buffer = new Buffer(newEntry.weightCheck.body, 'binary'); + lwip.open(buffer, 'png', function(err, image) { + image.width().should.equal(620); + image.height().should.equal(104); + done(err); + }); + + }) + + .fail(function(err) { + done(err); + }); + }); + it('should fail downloading a file in error', function(done) { var entry = { method: 'GET', @@ -103,7 +184,7 @@ describe('weightChecker', function() { weightChecker.redownloadEntry(entry) - .then(function(errnewEntry) { + .then(function(newEntry) { newEntry.weightCheck.should.have.a.property('message').that.equals('error while downloading: 404'); done(); diff --git a/test/fixtures/jpeg-image.jpg b/test/www/jpeg-image.jpg similarity index 100% rename from test/fixtures/jpeg-image.jpg rename to test/www/jpeg-image.jpg diff --git a/test/www/logo-large.png b/test/www/logo-large.png new file mode 100644 index 0000000000000000000000000000000000000000..f739504057983a007144315515a0a431463cce2e GIT binary patch literal 4193 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