Skip to content

Instantly share code, notes, and snippets.

@alexpchin
Forked from ifraixedes/LICENSE
Created July 15, 2016 12:41
Show Gist options
  • Save alexpchin/00fade76efe596baeafed86e64b951f8 to your computer and use it in GitHub Desktop.
Save alexpchin/00fade76efe596baeafed86e64b951f8 to your computer and use it in GitHub Desktop.
AWS lambda handler which download images from S3, resizes them and upload the new ones to S3; tested with mocha, chai, sinon and proxyquire
require('blanket')({
pattern: function (filename) {
return !/node_modules/.test(filename);
}
});
var proxyquire = require('proxyquire');
var chai = require('chai');
var sinonChai = require("sinon-chai");
var extend = require('lodash').extend;
var sinon = require('sinon');
chai.use(sinonChai);
describe('AwsHandler', function () {
var expect = chai.expect;
var sizesConfigs = [
{ width: 800, destinationPath: 'large' },
{ width: 500, destinationPath: 'medium' },
{ width: 200, destinationPath: 'small' },
{ width: 45, destinationPath: 'thumbnail'}
];
var baseEvent = {
"Records": [
{ "s3": {
"bucket": { "name": "testbucket" },
"object": { "key": null }
}
}
]
};
describe('reject to process non recognised image file extensions', function () {
var event, gmSpy, getObjectSpy, putObjectSpy, contextDoneSpy, testedModule;
before(function () {
event = extend({}, baseEvent);
event.Records[0].s3.object.key = "no-supported.gif";
gmSpy = sinon.spy();
getObjectSpy = sinon.spy();
putObjectSpy = sinon.spy();
contextDoneSpy = sinon.spy();
testedModule = getTestedModule(gmSpy, getObjectSpy, putObjectSpy);
testedModule.AwsHandler(event, { done: contextDoneSpy });
});
it('never call s3 getObject', function () {
expect(getObjectSpy).has.not.been.called;
});
it('never call graphics magick', function () {
expect(gmSpy).has.not.been.called;
});
it('never call s3 putObject', function () {
expect(putObjectSpy).has.not.been.called;
});
it('call context done with error', function () {
expect(contextDoneSpy).has.been.calledOnce.and.calledWith(new Error());
});
});
describe('process the image when the it has jpg extension', function () {
var event, gmStubs, getObjectStub, putObjectStub, contextDoneSpy, testedModule, fakeResponse;
before(function (done) {
fakeResponse = { Body: 'image content' };
event = extend({}, baseEvent);
event.Records[0].s3.object.key = "image.jpg";
gmStubs = getGmStubs();
getObjectStub = sinon.stub().callsArgWith(1, null, fakeResponse);
putObjectStub = sinon.stub().callsArgWith(1, null);
contextDoneSpy = sinon.spy();
testedModule = getTestedModule(gmStubs.gm, getObjectStub, putObjectStub);
testedModule.AwsHandler(event, { done: function () {
contextDoneSpy.apply(null, arguments);
done();
}});
});
it('call s3 getObject', function () {
expect(getObjectStub).has.been.calledOnce;
expect(getObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: event.Records[0].s3.object.key
});
});
it('call graphics magick', function () {
expect(gmStubs.gm).has.been.callCount(sizesConfigs.length);
expect(gmStubs.resize).has.been.callCount(sizesConfigs.length);
expect(gmStubs.toBuffer).has.been.callCount(sizesConfigs.length);
expect(gmStubs.gm).always.has.been.calledWith(fakeResponse.Body, event.Records[0].s3.object.key);
sizesConfigs.forEach(function(s) {
expect(gmStubs.resize).has.been.calledWith(s.width);
expect(gmStubs.toBuffer).has.been.calledWith('jpg');
});
});
it('call s3 putObject', function () {
expect(putObjectStub).has.been.callCount(sizesConfigs.length);
sizesConfigs.forEach(function(s) {
expect(putObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: 'dst/' + s.destinationPath + '/' + event.Records[0].s3.object.key,
Body: 'data',
ContentType: 'image/jpg'
});
});
});
it('call context done with no error', function () {
expect(contextDoneSpy).has.been.calledOnce.and.calledWith(null);
});
});
describe('process the image when the it has jpge extension', function () {
var event, gmStubs, getObjectStub, putObjectStub, contextDoneSpy, testedModule, fakeResponse;
before(function (done) {
fakeResponse = { Body: 'image content' };
event = extend({}, baseEvent);
event.Records[0].s3.object.key = "image.jpge";
gmStubs = getGmStubs();
getObjectStub = sinon.stub().callsArgWith(1, null, fakeResponse);
putObjectStub = sinon.stub().callsArgWith(1, null);
contextDoneSpy = sinon.spy();
testedModule = getTestedModule(gmStubs.gm, getObjectStub, putObjectStub);
testedModule.AwsHandler(event, { done: function () {
contextDoneSpy.apply(null, arguments);
done();
}});
});
it('call s3 getObject', function () {
expect(getObjectStub).has.been.calledOnce;
expect(getObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: event.Records[0].s3.object.key
});
});
it('call graphics magick', function () {
expect(gmStubs.gm).has.been.callCount(sizesConfigs.length);
expect(gmStubs.resize).has.been.callCount(sizesConfigs.length);
expect(gmStubs.toBuffer).has.been.callCount(sizesConfigs.length);
expect(gmStubs.gm).always.has.been.calledWith(fakeResponse.Body, event.Records[0].s3.object.key);
sizesConfigs.forEach(function(s) {
expect(gmStubs.resize).has.been.calledWith(s.width);
expect(gmStubs.toBuffer).has.been.calledWith('jpg');
});
});
it('call s3 putObject', function () {
expect(putObjectStub).has.been.callCount(sizesConfigs.length);
sizesConfigs.forEach(function(s) {
expect(putObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: 'dst/' + s.destinationPath + '/' + event.Records[0].s3.object.key,
Body: 'data',
ContentType: 'image/jpg'
});
});
});
it('call context done with no error', function () {
expect(contextDoneSpy).has.been.calledOnce.and.calledWith(null);
});
});
describe('process the image when the it has png extension', function () {
var event, gmStubs, getObjectStub, putObjectStub, contextDoneSpy, testedModule, fakeResponse;
before(function (done) {
fakeResponse = { Body: 'image content' };
event = extend({}, baseEvent);
event.Records[0].s3.object.key = "image.png";
gmStubs = getGmStubs();
getObjectStub = sinon.stub().callsArgWith(1, null, fakeResponse);
putObjectStub = sinon.stub().callsArgWith(1, null);
contextDoneSpy = sinon.spy();
testedModule = getTestedModule(gmStubs.gm, getObjectStub, putObjectStub);
testedModule.AwsHandler(event, { done: function () {
contextDoneSpy.apply(null, arguments);
done();
}});
});
it('call s3 getObject', function () {
expect(getObjectStub).has.been.calledOnce;
expect(getObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: event.Records[0].s3.object.key
});
});
it('call graphics magick', function () {
expect(gmStubs.gm).has.been.callCount(sizesConfigs.length);
expect(gmStubs.resize).has.been.callCount(sizesConfigs.length);
expect(gmStubs.toBuffer).has.been.callCount(sizesConfigs.length);
expect(gmStubs.gm).always.has.been.calledWith(fakeResponse.Body, event.Records[0].s3.object.key);
sizesConfigs.forEach(function(s) {
expect(gmStubs.resize).has.been.calledWith(s.width);
expect(gmStubs.toBuffer).has.been.calledWith('png');
});
});
it('call s3 putObject', function () {
expect(putObjectStub).has.been.callCount(sizesConfigs.length);
sizesConfigs.forEach(function(s) {
expect(putObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: 'dst/' + s.destinationPath + '/' + event.Records[0].s3.object.key,
Body: 'data',
ContentType: 'image/png'
});
});
});
it('call context done with no error', function () {
expect(contextDoneSpy).has.been.calledOnce.and.calledWith(null);
});
});
describe('process the image but image magick fails', function () {
var event, gmStubs, getObjectStub, putObjectSpy, contextDoneSpy, testedModule, fakeResponse;
before(function (done) {
fakeResponse = { Body: 'image content' };
event = extend({}, baseEvent);
event.Records[0].s3.object.key = "image.png";
var toBufferStub = sinon.stub().callsArgWith(1, new Error('Image resize failed'));
gmStubs = getGmStubs(toBufferStub);
getObjectStub = sinon.stub().callsArgWith(1, null, fakeResponse);
putObjectSpy = sinon.spy();
contextDoneSpy = sinon.spy();
testedModule = getTestedModule(gmStubs.gm, getObjectStub, putObjectSpy);
testedModule.AwsHandler(event, { done: function () {
contextDoneSpy.apply(null, arguments);
done();
}});
});
it('call s3 getObject', function () {
expect(getObjectStub).has.been.calledOnce;
expect(getObjectStub).has.been.calledWith({
Bucket: event.Records[0].s3.bucket.name,
Key: event.Records[0].s3.object.key
});
});
it('call graphics magick', function () {
expect(gmStubs.gm).has.been.callCount(sizesConfigs.length);
expect(gmStubs.resize).has.been.callCount(sizesConfigs.length);
expect(gmStubs.toBuffer).has.been.callCount(sizesConfigs.length);
expect(gmStubs.gm).always.has.been.calledWith(fakeResponse.Body, event.Records[0].s3.object.key);
sizesConfigs.forEach(function(s) {
expect(gmStubs.resize).has.been.calledWith(s.width);
expect(gmStubs.toBuffer).has.been.calledWith('png');
});
});
it('never call s3 putObject', function () {
expect(putObjectSpy).has.been.not.called;
});
it('call context done with no error', function () {
expect(contextDoneSpy).has.been.calledOnce.and.calledWith(new Error('Image resize failed'));
});
});
});
function getGmStubs(toBuffer) {
var toBuffer = toBuffer || sinon.stub().callsArgWith(1, null, 'data')
var resize = sinon.stub().returns({ toBuffer: toBuffer });
var gm = sinon.stub().returns({ resize: resize });
return {
gm: gm,
resize: resize,
toBuffer: toBuffer
};
}
function getTestedModule(gm, getObject, putObject) {
return proxyquire('../aws-handler.js', {
'gm': { subClass: function() { return gm; } },
'aws-sdk': {
"S3": function () {
return {
getObject: getObject,
putObject: putObject
};
}
}
});
}
'use strict';
var s3 = new (require('aws-sdk')).S3();
var gm = require('gm').subClass({ imageMagick: true });
var async = require('async');
var imageTypeRegExp = /(?:(jpg)e?|(png))$/;
var sizesConfigs = [
{ width: 800, destinationPath: 'large' },
{ width: 500, destinationPath: 'medium' },
{ width: 200, destinationPath: 'small' },
{ width: 45, destinationPath: 'thumbnail'}
];
exports.AwsHandler = function (event, context) {
var s3Bucket = event.Records[0].s3.bucket.name;
var s3Key = event.Records[0].s3.object.key;
// Check if file has a supported image extension
var imgExt = imageTypeRegExp.exec(s3Key);
if (imgExt === null) {
console.error('unable to infer the image type for key %s', s3Key);
context.done(new Error('unable to infer the image type for key %s' + s3Key));
return;
}
var imageType = imgExt[1] || imgExt[2];
async.waterfall([
function download(next) {
s3.getObject({
Bucket: s3Bucket,
Key: s3Key
}, next);
},
function transform(response, next) {
async.map(sizesConfigs, function (sizeConfig, mapNext) {
gm(response.Body, s3Key)
.resize(sizeConfig.width)
.toBuffer(imageType, function (err, buffer) {
if (err) {
mapNext(err);
return;
}
s3.putObject({
Bucket: s3Bucket,
Key: 'dst/' + sizeConfig.destinationPath + '/' + s3Key,
Body: buffer,
ContentType: 'image/' + imageType
}, mapNext)
});
}, next);
}
], function (err) {
if (err) {
console.error('Error processing image, details %s', err.message);
context.done(err);
} else {
context.done(null, 'Successfully resized images bucket: ' + s3Bucket + ' / key: ' + s3Key);
}
});
};
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004
(http://www.wtfpl.net/about/)
Copyright (C) 2015 Mario Mendes (@hyprstack)
Copyright (C) 2015 Ivan Fraixedes (https://ivan.fraixed.es)
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "aws-handler-image-resizer",
"private": true,
"version": "0.0.0",
"description": "",
"scripts": {
"test": "mocha -R html-cov > coverage.html"
},
"author": "Mario Mendes (@hyprstack)",
"contributors": [{
"name": "Ivan Fraixedes",
"email": "ivan@fraixed.es",
"url": "http://ivan.fraixed.es"
}],
"license": "wtfpl",
"dependencies": {
"async": "^0.9.0",
"aws-sdk": "^2.1.18",
"gm": "^1.17.0"
},
"devDependencies": {
"lodash": "^3.6.0",
"blanket": "^1.1.6",
"mocha": "^2.2.1",
"proxyquire": "^1.4.0",
"sinon": "^1.14.1",
"sinon-chai": "^2.7.0",
"chai": "^2.1.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment