From 92dcaea0e3b90bb30429b03317a38ba0a76a5ce8 Mon Sep 17 00:00:00 2001 From: Chris Charabaruk Date: Thu, 31 Mar 2016 15:47:56 -0400 Subject: [PATCH 1/5] Added support for subaccounts --- docs/resources/subaccounts.md | 39 ++++++++++++++ lib/subaccounts.js | 62 +++++++++++++++++++++++ test/spec/subaccounts.speq.js | 95 +++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 docs/resources/subaccounts.md create mode 100644 lib/subaccounts.js create mode 100644 test/spec/subaccounts.speq.js diff --git a/docs/resources/subaccounts.md b/docs/resources/subaccounts.md new file mode 100644 index 0000000..970e653 --- /dev/null +++ b/docs/resources/subaccounts.md @@ -0,0 +1,39 @@ +# Subaccounts + +This library provides easy access to the [Subaccounts](https://www.sparkpost.com/api#/reference/subaccounts) Resource. + +## Methods +* **all(callback)** + List a summary of all subaccounts. + * `callback` - executed after task is completed. **required** + * standard `callback(err, data)` + * `err` - any error that occurred + * `data` - full response from request client +* **find(options, callback)** + Retrieve details about a specified subaccount by its id + * `options.id` - the id of the subaccount you want to look up **required** + * `callback` - see all function +* **create(options, callback)** + Create a new subaccount + * `options.subaccount` - a subaccount object **required** + * `callback` - see all function +* **update(options, callback)** + Updates an existing subaccount + * `options.subaccount` - a subaccount object **required** + * `callback` - see all function + +## Examples + +```js +var SparkPost = require('sparkpost'); +var client = new SparkPost('YOUR_API_KEY'); + +client.subaccounts.all(function(err, data) { + if(err) { + console.log(err); + return; + } + + console.log(data.body); +}); +``` diff --git a/lib/subaccounts.js b/lib/subaccounts.js new file mode 100644 index 0000000..d8dc5b8 --- /dev/null +++ b/lib/subaccounts.js @@ -0,0 +1,62 @@ +'use strict'; + +var api = 'subaccounts' + , toApiFormat = require('./toApiFormat'); + +module.exports = function(client) { + var subaccounts = { + all: function(callback) { + var options = { + uri: api + }; + client.get(options, callback); + }, + find: function(options, callback) { + options = options || {}; + + if (!options.id) { + callback(new Error('subaccount id is required')); + return; + } + + var reqOpts = { + uri: api + '/' + options.id + }; + + client.get(reqOpts, callback); + }, + create: function(options, callback) { + options = options || {}; + + if (!options.subaccount) { + callback(new Error('subaccount object is required')); + return; + } + + var reqOpts = { + uri: api, + json: toApiFormat(options.subacount) + }; + + client.post(reqOpts, callback); + }, + update: function(options, callback) { + options = options || {}; + + if (!options.subaccount) { + callback(new Error('subaccount object is required')); + return; + } + + var object = toApiFormat(options.subaccount) + , reqOpts = { + uri: api + '/' + object.id, + json: object + }; + + client.put(reqOpts, callback); + } + }; + + return subaccounts; +}; diff --git a/test/spec/subaccounts.speq.js b/test/spec/subaccounts.speq.js new file mode 100644 index 0000000..979a08b --- /dev/null +++ b/test/spec/subaccounts.speq.js @@ -0,0 +1,95 @@ +var chai = require('chai') + , expect = chai.expect + , sinon = require('sinon') + , sinonChai = require('sinon-chai'); + +chai.use(sinonChai); + +describe('Subaccounts Library', function () { + var client, subaccounts; + + beforeEach(function() { + client = { + get: sinon.stub().yields(), + post: sinon.stub().yields(), + put: sinon.stub().yields() + }; + + subaccounts = require('../../lib/subaccounts')(client); + }); + + describe('all Method', function() { + it('should call client get method with the appropriate uri', function(done) { + subaccounts.all(function(err, data) { + expect(client.get.firstCall.args[0].uri).to.equal('subaccounts'); + done(); + }); + }); + }); + + describe('find Method', function() { + it('should call client get method with the appropriate uri', function(done) { + var options = { + id: 'test' + }; + subaccounts.find(options, function(err, data) { + expect(client.get.firstCall.args[0].uri).to.equal('subaccounts/test'); + done(); + }); + }); + + it('should throw an error if id is missing', function(done) { + subaccounts.find(null, function(err) { + expect(err.message).to.equal('subaccount id is required'); + expect(client.get).not.to.have.been.called; + done(); + }); + }); + }); + + describe('create Method', function() { + it('should call client post method with the appropriate uri', function(done) { + var options = { + subaccount: { + id: 'test' + } + }; + + subaccounts.create(options, function(err, data) { + expect(client.post.firstCall.args[0].uri).to.equal('subaccounts'); + done(); + }); + }); + + it('should throw an error if subaccount object is missing', function(done) { + subaccounts.create(null, function(err) { + expect(err.message).to.equal('subaccount object is required'); + expect(client.post).not.to.have.been.called; + done(); + }); + }); + }); + + describe('update Method', function() { + it('should call client put method with the appropriate uri', function(done) { + var options = { + subaccount: { + id: 'test' + } + }; + + subaccounts.update(options, function(err, data) { + expect(client.put.firstCall.args[0].uri).to.equal('subaccounts/test'); + done(); + }); + }); + + it('should throw an error if subaccount object is missing', function(done) { + subaccounts.update(null, function(err) { + expect(err.message).to.equal('subaccount object is required'); + expect(client.put).not.to.have.been.called; + done(); + }); + }); + }); +}); From 2d68b32725828cbef18e70849ca2676181b9b2d6 Mon Sep 17 00:00:00 2001 From: Chris Charabaruk Date: Thu, 31 Mar 2016 15:54:31 -0400 Subject: [PATCH 2/5] update README to list subaccounts library --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 264d02c..ad86d29 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ Click on the desired API to see usage and more information * [Recipient Lists](/docs/resources/recipientLists.md) - `client.recipientLists` ([examples](/examples/recipientLists)) * [Relay Webhooks](/docs/resources/relayWebhooks.md) - `client.relayWebhooks` ([examples](/examples/relayWebhooks)) * [Sending Domains](/docs/resources/sendingDomains.md) - `client.sendingDomains` ([examples](/examples/sendingDomains)) +* [Subaccounts](/docs/resources/subaccounts.md) - `client.subaccounts` ([examples](/examples/subaccounts)) * [Suppression List](/docs/resources/suppressionList.md) - `client.suppressionList` ([examples](/examples/suppressionList)) * [Templates](/docs/resources/templates.md) - `client.templates` ([examples](/examples/templates)) * [Transmissions](/docs/resources/transmissions.md) - `client.transmissions` ([examples](/examples/transmissions)) From 8bafacc8a0c59ad29fa55a5e1ba3ad7fc45e3dc2 Mon Sep 17 00:00:00 2001 From: Chris Charabaruk Date: Thu, 31 Mar 2016 17:26:16 -0400 Subject: [PATCH 3/5] rewrote subaccounts library to match new API style --- docs/resources/subaccounts.md | 15 +++-- lib/subaccounts.js | 87 +++++++++++++++++++++-------- test/spec/subaccounts.speq.js | 102 ++++++++++++++++++++++++++++------ 3 files changed, 161 insertions(+), 43 deletions(-) diff --git a/docs/resources/subaccounts.md b/docs/resources/subaccounts.md index 970e653..d948692 100644 --- a/docs/resources/subaccounts.md +++ b/docs/resources/subaccounts.md @@ -9,17 +9,24 @@ This library provides easy access to the [Subaccounts](https://www.sparkpost.com * standard `callback(err, data)` * `err` - any error that occurred * `data` - full response from request client -* **find(options, callback)** +* **find(subaccountId, callback)** Retrieve details about a specified subaccount by its id - * `options.id` - the id of the subaccount you want to look up **required** + * `subaccountId` - the id of the subaccount you want to look up **required** * `callback` - see all function * **create(options, callback)** Create a new subaccount - * `options.subaccount` - a subaccount object **required** + * `options.name` - user-friendly name **required** + * `options.keyLabel` - user-friendly identifier for subaccount API key **required** + * `options.keyGrants` - list of grants to give the subaccount API key **required** + * `options.keyValidIps` - list of IPs the subaccount may be used from + * `options.ipPool` - id of the default IP pool assigned to subaccount's transmissions * `callback` - see all function * **update(options, callback)** Updates an existing subaccount - * `options.subaccount` - a subaccount object **required** + * `options.subaccountId` - the id of the subaccount you want to update **required** + * `options.name` - user-friendly name + * `options.status` - status of the subaccount + * `options.ipPool` - id of the default IP pool assigned to subaccount's transmissions * `callback` - see all function ## Examples diff --git a/lib/subaccounts.js b/lib/subaccounts.js index d8dc5b8..5da4357 100644 --- a/lib/subaccounts.js +++ b/lib/subaccounts.js @@ -1,7 +1,24 @@ 'use strict'; -var api = 'subaccounts' - , toApiFormat = require('./toApiFormat'); +var api = 'subaccounts'; + +var toApiFormat = function(input) { + var model = {}; + + model.name = input.name; + model.key_label = input.keyLabel; + model.ip_pool = input.ipPool; + model.status = input.status; + + model.key_grants = Array.isArray(input.keyGrants) + ? input.keyGrants + : [input.keyGrants]; + model.key_valid_ips = Array.isArray(input.keyValidIps) + ? input.keyValidIps + : [input.keyValidIps]; + + return model; +} module.exports = function(client) { var subaccounts = { @@ -11,49 +28,73 @@ module.exports = function(client) { }; client.get(options, callback); }, - find: function(options, callback) { - options = options || {}; + find: function(subaccountId, callback) { + if(typeof subaccountId === 'function') { + callback = subaccountId; + subaccountId = null; + } - if (!options.id) { - callback(new Error('subaccount id is required')); + if (!subaccountId) { + callback(new Error('subaccountId is required')); return; } - var reqOpts = { - uri: api + '/' + options.id + var options = { + uri: api + '/' + subaccountId }; - - client.get(reqOpts, callback); + client.get(options, callback); }, create: function(options, callback) { - options = options || {}; + if(typeof options === 'function') { + callback = options; + options = null; + } - if (!options.subaccount) { - callback(new Error('subaccount object is required')); + if (!options) { + callback(new Error('options are required')); + return; + } + + if(!options.name) { + callback(new Error('name is required in options')); + return; + } + if(!options.keyLabel) { + callback(new Error('keyLabel is required in options')); + return; + } + if(!options.keyGrants) { + callback(new Error('keyGrants is required in options')); return; } var reqOpts = { uri: api, - json: toApiFormat(options.subacount) + json: toApiFormat(options) }; - client.post(reqOpts, callback); }, update: function(options, callback) { - options = options || {}; + if(typeof options === 'function') { + callback = options; + options = null; + } - if (!options.subaccount) { - callback(new Error('subaccount object is required')); + if(!options) { + callback(new Error('options are required')); return; } - var object = toApiFormat(options.subaccount) - , reqOpts = { - uri: api + '/' + object.id, - json: object - }; + if(!options.subaccountId) { + callback(new Error('subaccountId is required in options')) + return; + } + var subaccountId = options.subaccountId; + var reqOpts = { + uri: api + '/' + subaccountId, + json: toApiFormat(options) + }; client.put(reqOpts, callback); } }; diff --git a/test/spec/subaccounts.speq.js b/test/spec/subaccounts.speq.js index 979a08b..a69f804 100644 --- a/test/spec/subaccounts.speq.js +++ b/test/spec/subaccounts.speq.js @@ -29,18 +29,25 @@ describe('Subaccounts Library', function () { describe('find Method', function() { it('should call client get method with the appropriate uri', function(done) { - var options = { - id: 'test' - }; - subaccounts.find(options, function(err, data) { + var subaccountId = 'test'; + + subaccounts.find(subaccountId, function(err, data) { expect(client.get.firstCall.args[0].uri).to.equal('subaccounts/test'); done(); }); }); - it('should throw an error if id is missing', function(done) { + it('should throw an error if subaccountId is null', function(done) { subaccounts.find(null, function(err) { - expect(err.message).to.equal('subaccount id is required'); + expect(err.message).to.equal('subaccountId is required'); + expect(client.get).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if subaccountId is missing', function(done) { + subaccounts.find(function(err) { + expect(err.message).to.equal('subaccountId is required'); expect(client.get).not.to.have.been.called; done(); }); @@ -50,9 +57,9 @@ describe('Subaccounts Library', function () { describe('create Method', function() { it('should call client post method with the appropriate uri', function(done) { var options = { - subaccount: { - id: 'test' - } + name: 'test', + keyLabel: 'test', + keyGrants: [] }; subaccounts.create(options, function(err, data) { @@ -61,9 +68,56 @@ describe('Subaccounts Library', function () { }); }); - it('should throw an error if subaccount object is missing', function(done) { + it('should throw an error if options is null', function(done) { subaccounts.create(null, function(err) { - expect(err.message).to.equal('subaccount object is required'); + expect(err.message).to.equal('options are required'); + expect(client.post).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if options is missing', function(done) { + subaccounts.create(function(err) { + expect(err.message).to.equal('options are required'); + expect(client.post).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if name is missing from options', function(done) { + var options = { + keyLabel: 'test', + keyGrants: [] + }; + + subaccounts.create(options, function(err) { + expect(err.message).to.equal('name is required in options'); + expect(client.post).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if keyLabel is missing from options', function(done) { + var options = { + name: 'test', + keyGrants: [] + }; + + subaccounts.create(options, function(err) { + expect(err.message).to.equal('keyLabel is required in options'); + expect(client.post).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if keyGrants is missing from options', function(done) { + var options = { + name: 'test', + keyLabel: 'test' + }; + + subaccounts.create(options, function(err) { + expect(err.message).to.equal('keyGrants is required in options'); expect(client.post).not.to.have.been.called; done(); }); @@ -73,9 +127,7 @@ describe('Subaccounts Library', function () { describe('update Method', function() { it('should call client put method with the appropriate uri', function(done) { var options = { - subaccount: { - id: 'test' - } + subaccountId: 'test' }; subaccounts.update(options, function(err, data) { @@ -84,9 +136,27 @@ describe('Subaccounts Library', function () { }); }); - it('should throw an error if subaccount object is missing', function(done) { + it('should throw an error if options is null', function(done) { subaccounts.update(null, function(err) { - expect(err.message).to.equal('subaccount object is required'); + expect(err.message).to.equal('options are required'); + expect(client.put).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if options is missing', function(done) { + subaccounts.update(function(err) { + expect(err.message).to.equal('options are required'); + expect(client.put).not.to.have.been.called; + done(); + }); + }); + + it('should throw an error if subaccountId is missing from options', function(done) { + var options = {}; + + subaccounts.update(options, function(err, data) { + expect(err.message).to.equal('subaccountId is required in options'); expect(client.put).not.to.have.been.called; done(); }); From 876a2b24bdbc6f664edb9cc8bfc1e7e548fd4dc0 Mon Sep 17 00:00:00 2001 From: Chris Charabaruk Date: Thu, 31 Mar 2016 17:37:19 -0400 Subject: [PATCH 4/5] cleaned up complaints from jshint JSHint complains and dies because the validation for options brings the cyclomatic complexity of subaccounts.create to 6, where the settings say the maximum should be 5. Refactored the validation of options into its own function, but if there ever should be more required properties, then we'll be right back here again. --- lib/subaccounts.js | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/subaccounts.js b/lib/subaccounts.js index 5da4357..888a2a6 100644 --- a/lib/subaccounts.js +++ b/lib/subaccounts.js @@ -10,15 +10,27 @@ var toApiFormat = function(input) { model.ip_pool = input.ipPool; model.status = input.status; - model.key_grants = Array.isArray(input.keyGrants) - ? input.keyGrants - : [input.keyGrants]; - model.key_valid_ips = Array.isArray(input.keyValidIps) - ? input.keyValidIps - : [input.keyValidIps]; + model.key_grants = Array.isArray(input.keyGrants) ? input.keyGrants : [input.keyGrants]; + model.key_valid_ips = Array.isArray(input.keyValidIps) ? input.keyValidIps : [input.keyValidIps]; return model; -} +}; + +var validateCreateOptions = function(input) { + if (!input.name) { + return 'name is required'; + } + + if (!input.keyLabel) { + return 'keyLabel is required'; + } + + if (!input.keyGrants) { + return 'keyGrants is required'; + } + + return null; +}; module.exports = function(client) { var subaccounts = { @@ -55,16 +67,9 @@ module.exports = function(client) { return; } - if(!options.name) { - callback(new Error('name is required in options')); - return; - } - if(!options.keyLabel) { - callback(new Error('keyLabel is required in options')); - return; - } - if(!options.keyGrants) { - callback(new Error('keyGrants is required in options')); + var validation = validateCreateOptions(options); + if (validation) { + callback(new Error(validation + ' in options')); return; } @@ -86,7 +91,7 @@ module.exports = function(client) { } if(!options.subaccountId) { - callback(new Error('subaccountId is required in options')) + callback(new Error('subaccountId is required in options')); return; } From 96df8e38234471f3ef52e75856347b4989087589 Mon Sep 17 00:00:00 2001 From: Chris Charabaruk Date: Thu, 31 Mar 2016 21:19:01 -0400 Subject: [PATCH 5/5] renamed subaccounts spec file to match others --- test/spec/{subaccounts.speq.js => subaccounts.spec.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/spec/{subaccounts.speq.js => subaccounts.spec.js} (100%) diff --git a/test/spec/subaccounts.speq.js b/test/spec/subaccounts.spec.js similarity index 100% rename from test/spec/subaccounts.speq.js rename to test/spec/subaccounts.spec.js