Skip to content

Commit

Permalink
Merge pull request #471 from jjsquillante/master
Browse files Browse the repository at this point in the history
Stream support for FileUpload
  • Loading branch information
ob-stripe authored Jul 18, 2018
2 parents 6d3f58e + d963055 commit d597f14
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 20 deletions.
40 changes: 24 additions & 16 deletions lib/StripeResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,28 +241,36 @@ StripeResource.prototype = {
var self = this;
var requestData;

if (self.requestDataProcessor) {
requestData = self.requestDataProcessor(method, data, options.headers);
} else {
requestData = utils.stringifyRequestData(data || {});
}
function makeRequestWithData(error, data) {
var apiVersion;
var headers;

var apiVersion = this._stripe.getApiField('version');
if (error) {
return callback(error);
}

var headers = self._defaultHeaders(auth, requestData.length, apiVersion);
apiVersion = self._stripe.getApiField('version');
requestData = data;
headers = self._defaultHeaders(auth, requestData.length, apiVersion);

// Grab client-user-agent before making the request:
this._stripe.getClientUserAgent(function(cua) {
headers['X-Stripe-Client-User-Agent'] = cua;
self._stripe.getClientUserAgent(function(cua) {
headers['X-Stripe-Client-User-Agent'] = cua;

if (options.headers) {
Object.assign(headers, options.headers);
}
if (options.headers) {
Object.assign(headers, options.headers);
}

makeRequest();
});
makeRequest(apiVersion, headers);
});
}

if (self.requestDataProcessor) {
self.requestDataProcessor(method, data, options.headers, makeRequestWithData);
} else {
makeRequestWithData(null, utils.stringifyRequestData(data || {}));
}

function makeRequest() {
function makeRequest(apiVersion, headers) {
var timeout = self._stripe.getApiField('timeout');
var isInsecureConnection = self._stripe.getApiField('protocol') == 'http';

Expand Down
53 changes: 50 additions & 3 deletions lib/resources/FileUploads.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,68 @@
'use strict';

var Buffer = require('safe-buffer').Buffer;
var utils = require('../utils');
var StripeResource = require('../StripeResource');
var stripeMethod = StripeResource.method;
var multipartDataGenerator = require('../MultipartDataGenerator');
var Error = require('../Error');

module.exports = StripeResource.extend({

overrideHost: 'uploads.stripe.com',

requestDataProcessor: function(method, data, headers) {
requestDataProcessor: function(method, data, headers, callback) {
data = data || {};

if (method === 'POST') {
return multipartDataGenerator(method, data, headers);
return getProcessorForSourceType(data);
} else {
return utils.stringifyRequestData(data);
return callback(null, utils.stringifyRequestData(data));
}

function getProcessorForSourceType(data) {
var isStream = utils.checkForStream(data);
if (isStream) {
return streamProcessor(multipartDataGenerator);
} else {
var buffer = multipartDataGenerator(method, data, headers);
return callback(null, buffer);
}
}

function streamProcessor (fn) {
var bufferArray = [];
data.file.data.on('data', function(line) {
bufferArray.push(line);
}).on('end', function() {
var bufferData = Object.assign({}, data);
bufferData.file.data = Buffer.concat(bufferArray);
var buffer = fn(method, bufferData, headers);
callback(null, buffer);
}).on('error', function(err) {
var errorHandler = streamError(callback);
errorHandler(err, null);
});
}

function streamError(callback) {
var StreamProcessingError = Error.extend({
type: 'StreamProcessingError',
populate: function(raw) {
this.type = this.type;
this.message = raw.message;
this.detail = raw.detail;
}
});
return function(error) {
callback(
new StreamProcessingError({
message: 'An error occurred while attempting to process the file for upload.',
detail: error,
}),
null
);
}
}
},

Expand Down
12 changes: 12 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var Buffer = require('safe-buffer').Buffer;
var EventEmitter = require('events').EventEmitter;
var qs = require('qs');
var crypto = require('crypto');

Expand Down Expand Up @@ -215,6 +216,17 @@ var utils = module.exports = {

return obj;
},

/**
* Determine if file data is a derivative of EventEmitter class.
* https://nodejs.org/api/events.html#events_events
*/
checkForStream: function (obj) {
if (obj.file && obj.file.data) {
return obj.file.data instanceof EventEmitter;
}
return false;
},
};

function emitWarning(warning) {
Expand Down
67 changes: 66 additions & 1 deletion test/flows.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ var stripe = require('../lib/stripe')(
testUtils.getUserStripeKey(),
'latest'
);

var fs = require('fs');
var path = require('path');
var stream = require('stream');
var expect = chai.expect;

var CUSTOMER_DETAILS = {
Expand Down Expand Up @@ -522,4 +524,67 @@ describe('Flows', function() {
}).type).to.equal('StripeInvalidRequestError');
});
});

describe('FileUpload', function() {
it('Allows you to upload a file as a stream', function() {
var testFilename = path.join(__dirname, 'resources/data/minimal.pdf');
var f = fs.createReadStream(testFilename);

return expect(
stripe.fileUploads.create({
purpose: 'dispute_evidence',
file: {
data: f,
name: 'minimal.pdf',
type: 'application/octet-stream',
},
}).then(null, function(error) {
return error;
})
).to.eventually.have.nested.property('size', 739);
});

it('Allows you to upload a file synchronously', function() {
var testFilename = path.join(__dirname, 'resources/data/minimal.pdf');
var f = fs.readFileSync(testFilename);

return expect(
stripe.fileUploads.create({
purpose: 'dispute_evidence',
file: {
data: f,
name: 'minimal.pdf',
type: 'application/octet-stream',
},
}).then(null, function(error) {
return error;
})
).to.eventually.have.nested.property('size', 739);
});

it('Surfaces stream errors correctly', function(done) {
var mockedStream = new stream.Readable();
mockedStream._read = function() {};

var fakeError = new Error('I am a fake error');

process.nextTick(function() {
mockedStream.emit('error', fakeError);
});

stripe.fileUploads.create({
purpose: 'dispute_evidence',
file: {
data: mockedStream,
name: 'minimal.pdf',
type: 'application/octet-stream',
},
}).catch(function(error) {
expect(error.message).to.equal('An error occurred while attempting to process the file for upload.');
expect(error.detail).to.equal(fakeError);

done();
});
});
});
});
35 changes: 35 additions & 0 deletions test/resources/FileUploads.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,40 @@ describe('File Uploads Resource', function() {
expect(stripe.LAST_REQUEST).to.deep.property('url', '/v1/files');
expect(stripe.LAST_REQUEST).to.deep.property('auth', TEST_AUTH_KEY);
});

it('Streams a file and sends the correct file upload request', function() {
var testFilename = path.join(__dirname, 'data/minimal.pdf');
var f = fs.createReadStream(testFilename);

return stripe.fileUploads.create({
purpose: 'dispute_evidence',
file: {
data: f,
name: 'minimal.pdf',
type: 'application/octet-stream',
},
}).then(function() {
expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST');
expect(stripe.LAST_REQUEST).to.deep.property('url', '/v1/files');
});
});

it('Streams a file and sends the correct file upload request [with specified auth]', function() {
var testFilename = path.join(__dirname, 'data/minimal.pdf');
var f = fs.createReadStream(testFilename);

return stripe.fileUploads.create({
purpose: 'dispute_evidence',
file: {
data: f,
name: 'minimal.pdf',
type: 'application/octet-stream',
},
}, TEST_AUTH_KEY).then(function() {
expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST');
expect(stripe.LAST_REQUEST).to.deep.property('url', '/v1/files');
expect(stripe.LAST_REQUEST).to.deep.property('auth', TEST_AUTH_KEY);
});
});
});
});

0 comments on commit d597f14

Please sign in to comment.