Socket
Socket
Sign inDemoInstall

ebay-api

Package Overview
Dependencies
6
Maintainers
1
Versions
73
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.0 to 1.2.0

lib/errors.js

13

index.js
// eBay API client for Node.js
exports.xmlRequest = require('./lib/xml-request').xmlRequest;
var _ = require('lodash');
exports.paginatedRequest = require('./lib/pagination').paginatedRequest;
var parser = require('./lib/parser');
exports.parseResponse = parser.parseResponse;
exports.flatten = parser.flatten;
module.exports = _.extend({},
require('./lib/xml-request'),
require('./lib/pagination'),
require('./lib/parser'),
require('./lib/errors')
);
var
_ = require('lodash'),
util = require('util'),
debug = require('debug')('ebay:parser');
debug = require('debug')('ebay:parser'),
_errors = require('./errors'),
EbaySystemError = _errors.EbaySystemError,
EbayRequestError = _errors.EbayRequestError,
EbayClientError = _errors.EbayClientError;
/*

@@ -142,6 +147,7 @@ helper: find an array containing only special key-value pairs

@param options: context on the request.
@return parsed object.
@throws exception on failure, or if error message is parsed from response.
@param callback: gets `null, data` in success case, and `error, data` on error case.
- error can be from response or parsing failure. (see error types.)
- callback is actually called immediately/synchronously - just using to have 2 return values in error case.
*/
exports.parseResponse = function(data, options) {
exports.parseResponse = function(data, options, callback) {
debug('parsing items', data);

@@ -157,29 +163,68 @@

if (_.isUndefined(data)) {
throw new Error("Response missing " + responseKey + " element");
// assume this is a failure of the client to parse the response properly.
throw new EbayClientError("Response missing " + responseKey + " element");
}
// 'ack' and 'errorMessage' indicate errors.
// in FindingService it's nested (?), in others it's flat - flatten to normalize.
var errorMessage, errors;
if (!_.isUndefined(data.Ack)) { // normalize to lowercase
data.ack = data.Ack;
delete data.Ack;
}
//
// 'Ack', 'Errors', (and maybe 'errorMessage'?) indicate errors.
// see http://developer.ebay.com/devzone/xml/docs/Reference/ebay/Errors/ErrorMessages.htm
//
var
errorMessage, // build a string
errorClassification = 'RequestError', // 'RequestError' or 'SystemError'
errors; // error object(s) in response.
// normalize to uppercase
if (!_.isUndefined(data.ack)) {
data.ack = flatten(data.ack);
data.Ack = data.ack;
delete data.ack;
}
if (_.isUndefined(data.ack) || data.ack !== 'Success') {
if (!_.isUndefined(data.errorMessage)) {
errorMessage = flatten(data.errorMessage);
if (_.isObject(errorMessage)) errorMessage = util.inspect(errorMessage, true, 3);
}
else if (!_.isUndefined(data.Errors)) {
if (!_.isUndefined(data.Ack)) {
data.Ack = flatten(data.Ack);
}
//
// note: docs say,
// "Both Success and Warning indicate that the request was successful.
// However, a value of Warning means that something occurred
// that may affect your application or the user."
// for now, treat Warning as a failure.
//
if (_.isUndefined(data.Ack) || data.Ack !== 'Success') {
//
// handle all different ways errors can be represented
//
// Trading, Shopping, Finding(?)
if (!_.isUndefined(data.Errors)) {
errors = _.isArray(data.Errors) ? data.Errors : [data.Errors];
// build composite message.
errorMessage = errors.map(function(errorObj) {
errorObj = flatten(errorObj);
return errorObj.LongMessage + '(' + errorObj.ErrorCode + ').';
}).join(' ');
if (errorObj.ErrorClassification === 'SystemError') {
errorClassification = 'SystemError'; // trumps RequestError
}
return errorObj.LongMessage + (errorObj.ErrorCode ? ' (' + errorObj.ErrorCode + ')' : '');
}).join(', ');
}
debug('data errors', data.ack, errorMessage);
throw new Error(util.format("Bad ack code: ", data.ack, errorMessage ? ' - ' + errorMessage : ''));
// @review which API is this for?
// (maybe a relic of JSON response, no longer relevant?)
else if (!_.isUndefined(data.errorMessage)) {
errorMessage = flatten(data.errorMessage);
if (_.isObject(errorMessage)) errorMessage = util.inspect(errorMessage, true, 3);
// TODO error code and classification in this format?
}
debug('response error', errorClassification, data.Ack, errorMessage);
if (!errorMessage) errorMessage = util.format("Bad ack code: ", data.Ack); // fallback
if (errorClassification === 'SystemError') {
return callback(new EbaySystemError("eBay API system error: " + errorMessage), data);
}
else {
return callback(new EbayRequestError("eBay API request error: " + errorMessage), data);
}
}

@@ -200,3 +245,3 @@

return data;
callback(null, data);
};

@@ -11,5 +11,11 @@ var

getDefaultHeaders = require('./defaults').getDefaultHeaders,
defaultParser = require('./parser').parseResponse;
defaultParser = require('./parser').parseResponse,
_errors = require('./errors'),
EbaySystemError = _errors.EbaySystemError,
EbayRequestError = _errors.EbayRequestError,
EbayClientError = _errors.EbayClientError;
/**

@@ -142,9 +148,9 @@ * handle quirk of 'xml' module:

//if (response.body instanceof Error) { // @review necessary?
// var error = response.body;
// error.message = "Completed with error: " + error.message;
// return callback(error);
//}
// this is tricky -- API should return 200 with body in every valid scenario,
// so a non-200 probably means something's wrong on the client side with the request.
// so expect an EbayClientError.
if (response.statusCode !== 200) {
return callback(new Error(util.format("Bad response status code", response.statusCode, response.body.toString())));
return callback(new EbayClientError(
util.format("Bad response status code", response.statusCode, (response.body ? response.body.toString() : null))
));
}

@@ -163,5 +169,4 @@

if (error) {
error.message = "Error parsing XML: " + error.message;
debug(error);
return next(error);
return next(new EbayClientError("Error parsing XML: " + error.message));
}

@@ -174,15 +179,24 @@ debug('Parsed XML', data);

function _parseJson(data, next) {
var parsed;
try {
parsed = options.parser(data, options);
next(null, parsed);
// will pass error if response includes an error message,
// or if actually can't parse.
options.parser(data, options, next);
}
],
function _done(error, data) {
if (error) {
if (/Ebay/i.test(error.name)) {
callback(error, data); // already differentiated
}
catch(error) {
error.message = "Error parsing JSON: " + error.message;
next(error)
else {
// some other parsing error
// @review - is the original stack trace lost here?
callback(new EbayClientError("Error parsing JSON: " + error.message), data);
}
}
],
callback);
else {
callback(null, data);
}
});
});
};
{
"name": "ebay-api",
"description": "eBay API Client",
"version": "1.1.0",
"version": "1.2.0",
"homepage": "https://github.com/newleafdigital/nodejs-ebay-api",

@@ -6,0 +6,0 @@ "author": "Ben Buckman <ben@newleafdigital.com> (http://newleafdigital.com)",

@@ -98,9 +98,2 @@ eBay API client for Node.js

## Debugging
This module uses the [debug](https://github.com/visionmedia/debug) module for internal logging.
Run your app (or node REPL) with `DEBUG=ebay* ...` to see output.
## Helpers

@@ -132,2 +125,10 @@

## Errors
The client exports and attempts to differentiate between `EbaySystemError`, `EbayRequestError`, and `EbayClientError`.
See http://developer.ebay.com/DevZone/Shopping/docs/CallRef/types/ErrorClassificationCodeType.html
and http://developer.ebay.com/devzone/xml/docs/Reference/ebay/Errors/ErrorMessages.htm.
## Examples

@@ -143,2 +144,9 @@

## Debugging
This module uses the [debug](https://github.com/visionmedia/debug) module for internal logging.
Run your app (or node REPL) with `DEBUG=ebay* ...` to see output.
Enjoy!

@@ -145,0 +153,0 @@

@@ -10,157 +10,166 @@ var

request = require('request'),
xmlRequest = require('../index').xmlRequest;
ebay = require('../index')
xmlRequest = ebay.xmlRequest;
describe('XML requests', function() {
beforeEach(function() {
sinon.stub(request, 'post', function(options, callback) {
process.nextTick(function() {
callback(null, {statusCode: 200}, {});
});
});
beforeEach('sandbox', function() {
this.sinon = sinon.sandbox.create();
});
afterEach(function() {
request.post.restore();
afterEach('unstub', function() {
this.sinon.restore();
});
describe('Shopping: GetSingleItem', function() {
beforeEach('build request', function() {
xmlRequest({
serviceName: 'Shopping',
opType: 'GetSingleItem',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemID': '123456'
},
reqOptions: {
describe('building requests', function() {
beforeEach('stub request', function () {
this.sinon.stub(request, 'post', function (options, callback) {
process.nextTick(function () {
callback(null, {statusCode: 200}, {});
});
});
});
describe('Shopping: GetSingleItem', function () {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Shopping',
opType: 'GetSingleItem',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemID': '123456'
},
reqOptions: {
headers: {
'X-Extra': 'um'
}
}
}, function noop() {
});
});
it('initiated request with expected parameters', function () {
expect(request.post).to.have.been.calledOnce;
expect(request.post.lastCall.args[0]).to.deep.equal({
url: 'http://open.api.sandbox.ebay.com/shopping',
headers: {
'Content-Type': 'text/xml',
'X-EBAY-API-APP-ID': 'ABCDEF',
'X-EBAY-API-CALL-NAME': 'GetSingleItem',
'X-EBAY-API-VERSION': '897',
'X-EBAY-API-SITE-ID': '0',
'X-EBAY-API-REQUEST-ENCODING': 'XML',
'X-EBAY-API-RESPONSE-ENCODING': 'XML',
'X-Extra': 'um'
}
}
}, function noop(){});
},
body: '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetSingleItemRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemID>123456</ItemID>\n' +
'</GetSingleItemRequest>'
});
})
});
it('initiated request with expected parameters', function() {
expect(request.post).to.have.been.calledOnce;
expect(request.post.lastCall.args[0]).to.deep.equal({
url: 'http://open.api.sandbox.ebay.com/shopping',
headers: {
'Content-Type': 'text/xml',
'X-EBAY-API-APP-ID': 'ABCDEF',
'X-EBAY-API-CALL-NAME': 'GetSingleItem',
'X-EBAY-API-VERSION': '897',
'X-EBAY-API-SITE-ID': '0',
'X-EBAY-API-REQUEST-ENCODING': 'XML',
'X-EBAY-API-RESPONSE-ENCODING': 'XML',
'X-Extra': 'um'
},
body: '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetSingleItemRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemID>123456</ItemID>\n' +
'</GetSingleItemRequest>'
describe('Shopping: GetMultipleItems', function () {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Shopping',
opType: 'GetMultipleItems',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemID': ['123456', '345678']
}
}, function noop() {
});
});
})
});
describe('Shopping: GetMultipleItems', function() {
beforeEach('build request', function() {
xmlRequest({
serviceName: 'Shopping',
opType: 'GetMultipleItems',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemID': ['123456', '345678']
}
}, function noop(){});
it('handles multiple parameter values', function () {
expect(request.post).to.have.been.calledOnce;
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetMultipleItemsRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemID>123456</ItemID>\n' +
' <ItemID>345678</ItemID>\n' +
'</GetMultipleItemsRequest>'
);
})
});
it('handles multiple parameter values', function() {
expect(request.post).to.have.been.calledOnce;
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetMultipleItemsRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemID>123456</ItemID>\n' +
' <ItemID>345678</ItemID>\n' +
'</GetMultipleItemsRequest>'
);
})
});
describe('Trading: GetOrders (authenticated)', function () {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders',
sandbox: true,
appId: 'ABCDEF',
authToken: 'super-secret',
raw: true, // no parsing
params: {
'NumberOfDays': '30'
}
}, function noop() {
});
});
describe('Trading: GetOrders (authenticated)', function() {
beforeEach('build request', function() {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders',
sandbox: true,
appId: 'ABCDEF',
authToken: 'super-secret',
raw: true, // no parsing
params: {
'NumberOfDays': '30'
}
}, function noop(){});
it('adds token to XML request', function () {
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetOrdersRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <RequesterCredentials>\n' +
' <eBayAuthToken>super-secret</eBayAuthToken>\n' +
' </RequesterCredentials>\n' +
' <NumberOfDays>30</NumberOfDays>\n' +
'</GetOrdersRequest>'
);
});
});
it('adds token to XML request', function() {
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetOrdersRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <RequesterCredentials>\n' +
' <eBayAuthToken>super-secret</eBayAuthToken>\n' +
' </RequesterCredentials>\n' +
' <NumberOfDays>30</NumberOfDays>\n' +
'</GetOrdersRequest>'
);
});
});
describe('Finding: findItemsByKeywords', function() {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Finding',
opType: 'findItemsByKeywords',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
keywords: ['Canon', 'Powershot'],
outputSelector: ['AspectHistogram'],
itemFilter: [
{name: 'FreeShippingOnly', value: 'true'},
{name: 'MaxPrice', value: '150'},
],
domainFilter: [
{name: 'domainName', value: 'Digital_Cameras'}
],
paginationInput: {
entriesPerPage: '5'
describe('Finding: findItemsByKeywords', function () {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Finding',
opType: 'findItemsByKeywords',
sandbox: true,
appId: 'ABCDEF',
raw: true, // no parsing
params: {
keywords: ['Canon', 'Powershot'],
outputSelector: ['AspectHistogram'],
itemFilter: [
{name: 'FreeShippingOnly', value: 'true'},
{name: 'MaxPrice', value: '150'},
],
domainFilter: [
{name: 'domainName', value: 'Digital_Cameras'}
],
paginationInput: {
entriesPerPage: '5'
}
}
}
}, function noop() {
}, function noop() {
});
});
});
it('initiated request with expected parameters', function() {
expect(request.post).to.have.been.calledOnce;
it('initiated request with expected parameters', function () {
expect(request.post).to.have.been.calledOnce;
expect(request.post.lastCall.args[0]).to.deep.equal({
url: 'http://svcs.sandbox.ebay.com/services/search/FindingService/v1',
headers: {
'X-EBAY-SOA-SECURITY-APPNAME': 'ABCDEF',
'X-EBAY-SOA-REQUEST-DATA-FORMAT': 'XML',
'X-EBAY-SOA-RESPONSE-DATA-FORMAT': 'XML',
'X-EBAY-SOA-GLOBAL-ID': 'EBAY-US',
'X-EBAY-SOA-SERVICE-VERSION': '1.13.0',
'X-EBAY-SOA-OPERATION-NAME': 'findItemsByKeywords'
},
body:
'<?xml version="1.0" encoding="UTF-8"?>\n' +
expect(request.post.lastCall.args[0]).to.deep.equal({
url: 'http://svcs.sandbox.ebay.com/services/search/FindingService/v1',
headers: {
'X-EBAY-SOA-SECURITY-APPNAME': 'ABCDEF',
'X-EBAY-SOA-REQUEST-DATA-FORMAT': 'XML',
'X-EBAY-SOA-RESPONSE-DATA-FORMAT': 'XML',
'X-EBAY-SOA-GLOBAL-ID': 'EBAY-US',
'X-EBAY-SOA-SERVICE-VERSION': '1.13.0',
'X-EBAY-SOA-OPERATION-NAME': 'findItemsByKeywords'
},
body: '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<findItemsByKeywordsRequest xmlns="http://www.ebay.com/marketplace/search/v1/services">\n' +

@@ -186,40 +195,196 @@ ' <keywords>Canon</keywords>\n' +

'</findItemsByKeywordsRequest>'
});
});
});
describe('nested params', function () {
beforeEach('build request', function () {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrderTransactions',
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemTransactionIDArray': [{
'ItemTransactionID': {
'OrderLineItemID': '12345-67890',
'SomeOtherID': 'ABCDEF'
}
}]
}
}, function noop() {
});
});
it('converts properly to nested XML', function () {
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetOrderTransactionsRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemTransactionIDArray>\n' +
' <ItemTransactionID>\n' +
' <OrderLineItemID>12345-67890</OrderLineItemID>\n' +
' <SomeOtherID>ABCDEF</SomeOtherID>\n' +
' </ItemTransactionID>\n' +
' </ItemTransactionIDArray>\n' +
'</GetOrderTransactionsRequest>'
);
})
});
});
describe('nested params', function() {
beforeEach('build request', function() {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrderTransactions',
appId: 'ABCDEF',
raw: true, // no parsing
params: {
'ItemTransactionIDArray': [{
'ItemTransactionID': {
'OrderLineItemID': '12345-67890',
'SomeOtherID': 'ABCDEF'
}
}]
}
}, function noop(){});
describe('response error handling', function() {
var err, data;
beforeEach(function() {
err = null;
data = null;
});
it('converts properly to nested XML', function() {
expect(request.post.lastCall.args[0]).to.have.property('body',
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetOrderTransactionsRequest xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <ItemTransactionIDArray>\n' +
' <ItemTransactionID>\n' +
' <OrderLineItemID>12345-67890</OrderLineItemID>\n' +
' <SomeOtherID>ABCDEF</SomeOtherID>\n' +
' </ItemTransactionID>\n' +
' </ItemTransactionIDArray>\n' +
'</GetOrderTransactionsRequest>'
);
})
});
function _buildResponseCallback(errorCode, errorClassification) {
return function(options, callback) {
process.nextTick(function() {
callback(null, {
statusCode: 200,
body: '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<GetOrdersResponse xmlns="urn:ebay:apis:eBLBaseComponents">\n' +
' <Ack>Failure</Ack>\n' +
' <Errors>\n' +
' <ShortMessage>Something went wrong.</ShortMessage>\n' +
' <LongMessage>Something really went wrong.</LongMessage>\n' +
' <ErrorCode>' + errorCode + '</ErrorCode>\n' +
' <SeverityCode>Error</SeverityCode>\n' +
' <ErrorClassification>' + errorClassification + '</ErrorClassification>\n' +
' </Errors>' +
'</GetOrdersResponse>' // (not comprehensive)
});
});
};
}
describe('RequestError from response', function() {
beforeEach('stub request', function() {
this.sinon.stub(request, 'post', _buildResponseCallback('12345', 'RequestError'));
});
beforeEach('simulate request', function (done) {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders'
}, function(_err, _data) {
err = _err;
data = _data;
done();
});
});
it('throws an EbayRequestError', function() {
expect(err).to.be.an.instanceOf(ebay.EbayRequestError);
});
it('also gets response data', function() {
expect(data).to.be.ok;
expect(data).to.have.property('Ack', 'Failure');
})
});
describe('SystemError from response', function() {
beforeEach('stub request', function() {
this.sinon.stub(request, 'post', _buildResponseCallback('12345', 'SystemError'));
});
beforeEach('simulate request', function (done) {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders'
}, function(_err, _data) {
err = _err;
data = _data;
done();
});
});
it('throws an EbaySystemError', function() {
expect(err).to.be.an.instanceOf(ebay.EbaySystemError);
});
});
describe('XML parsing fails', function() {
beforeEach('stub request', function() {
this.sinon.stub(request, 'post', _buildResponseCallback('<<<<', '>>>>')); // screw up XML body
});
beforeEach('simulate request', function (done) {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders'
}, function(_err, _data) {
err = _err;
data = _data;
done();
});
});
it('throws an EbayClientError', function() {
expect(err).to.be.an.instanceOf(ebay.EbayClientError);
})
});
describe('non-200 response code', function() {
beforeEach('stub request', function () {
this.sinon.stub(request, 'post', function (options, callback) {
process.nextTick(function () {
callback(null, {statusCode: 503});
});
});
});
beforeEach('simulate request', function (done) {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders'
}, function(_err) {
err = _err;
done();
});
});
it('throws an EbayClientError', function() {
expect(err).to.be.an.instanceOf(ebay.EbayClientError);
expect(err.message).to.match(/503/);
})
});
describe('other client error', function() {
beforeEach('stub request', function () {
this.sinon.stub(request, 'post', function (options, callback) {
process.nextTick(function () {
callback(null, {statusCode: 200, body: ''});
});
});
});
beforeEach('simulate request', function (done) {
xmlRequest({
serviceName: 'Trading',
opType: 'GetOrders',
parser: function(data, options, callback) {
callback(new Error("Boo"));
}
}, function(_err) {
err = _err;
done();
});
});
it('throws an EbayClientError', function() {
expect(err).to.be.an.instanceOf(ebay.EbayClientError);
expect(err.message).to.match(/Boo/);
})
});
})
});

@@ -226,0 +391,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc