Repository: auth0/node-auth0
Pull request #332 (2019-02-05 22:20:29)

Add delete user-blocks api
### Changes

- Created UserBlocksManager based on UsersManager.
- Supports calling the existing management api to delete user-blocks by id

### Testing

Created `user-blocks.test.js` based on `users.test.js`. The new test file passes.


shenie: @lbalmaceda sure - will have a crack at adding the other 3 endpoints.
shenie: @lbalmaceda I've added the 3 new endpoints you requests. Please have a look.
shenie: @luisrudge I created the tests for management/index.js in test/management/user-blocks.tests.js following the existing convention. Is there anything I missed in the test cases?
luisrudge: Your tests are covering the `UserBlocksManager` class. You need to add tests for the new methods you added here as well: https://github.com/auth0/node-auth0/blob/master/test/management/management-client.tests.js
shenie: Ah thanks for the tip @luisrudge.

I've pushed the requested changes.
luisrudge: @shenie there's some code paths that don't have a test. You can see them here: https://codecov.io/gh/auth0/node-auth0/compare/699f285251275c0caa6ee7eec8a2624ea5dab0bc...cc85c35919beb8c7ed15fde05dff417cb03a7ae7/diff#D4-80

Can you add a test for those cases so we can merge this? Thanks!

Patch:
diff --git a/src/management/UserBlocksManager.js b/src/management/UserBlocksManager.js
new file mode 100644
index 000000000..3794d7e69
--- /dev/null
+++ b/src/management/UserBlocksManager.js
@@ -0,0 +1,176 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var Auth0RestClient = require('../Auth0RestClient');
+var RetryRestClient = require('../RetryRestClient');
+
+/**
+ * Simple facade for consuming a REST API endpoint.
+ * @external RestClient
+ * @see https://github.com/ngonzalvez/rest-facade
+ */
+
+/**
+ * @class
+ * Abstracts interaction with the user-blocks endpoint.
+ * @constructor
+ * @memberOf module:management
+ *
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
+ * @param {Object} [options.retry]    Retry Policy Config
+ */
+var UserBlocksManager = function(options) {
+  if (options === null || typeof options !== 'object') {
+    throw new ArgumentError('Must provide manager options');
+  }
+
+  if (options.baseUrl === null || options.baseUrl === undefined) {
+    throw new ArgumentError('Must provide a base URL for the API');
+  }
+
+  if ('string' !== typeof options.baseUrl || options.baseUrl.length === 0) {
+    throw new ArgumentError('The provided base URL is invalid');
+  }
+
+  var clientOptions = {
+    errorFormatter: { message: 'message', name: 'error' },
+    headers: options.headers,
+    query: { repeatParams: false }
+  };
+
+  var userBlocksByIdResource = new Auth0RestClient(
+    options.baseUrl + '/user-blocks/:id',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.userBlocksById = new RetryRestClient(userBlocksByIdResource, options.retry);
+
+  var userBlocksByIdentifierResource = new Auth0RestClient(
+    options.baseUrl + '/user-blocks',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.userBlocksByIdentifier = new RetryRestClient(userBlocksByIdentifierResource, options.retry);
+};
+
+/**
+ * Get user blocks by id.
+ *
+ * @method    get
+ * @memberOf  module:management.UserBlocksManager.prototype
+ *
+ * @example
+ * management.userBlocks.get({ id: USER_ID }, function (err, blocks) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(blocks);
+ * });
+ *
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+UserBlocksManager.prototype.get = function(params) {
+  if (typeof params !== 'object' || typeof params.id !== 'string') {
+    throw new ArgumentError('You must provide an user id for the get method');
+  }
+
+  return this.userBlocksById.get.apply(this.userBlocksById, arguments);
+};
+
+/**
+ * Unblock an user by its id.
+ *
+ * @method    delete
+ * @memberOf  module:management.UserBlocksManager.prototype
+ *
+ * @example
+ * management.userBlocks.delete({ id: USER_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // User unblocked.
+ * });
+ *
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+UserBlocksManager.prototype.delete = function(params) {
+  if (typeof params !== 'object' || typeof params.id !== 'string') {
+    throw new ArgumentError('You must provide an user id for the delete method');
+  }
+
+  return this.userBlocksById.delete.apply(this.userBlocksById, arguments);
+};
+
+/**
+ * Get user blocks by identifier.
+ *
+ * @method    getByIdentifier
+ * @memberOf  module:management.UserBlocksManager.prototype
+ *
+ * @example
+ * management.userBlocks.getByIdentifier({ identifier: USER_ID }, function (err, blocks) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(blocks);
+ * });
+ *
+ *
+ * @param   {Object}    params              The user data object..
+ * @param   {String}    params.identifier   The user identifier, any of: username, phone_number, email.
+ * @param   {Function}  [cb]                Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+ UserBlocksManager.prototype.getByIdentifier = function(params) {
+   if (typeof params !== 'object' || typeof params.identifier !== 'string') {
+     throw new ArgumentError('You must provide an user identifier for the getByIdentifier method');
+   }
+
+   return this.userBlocksByIdentifier.get.apply(this.userBlocksByIdentifier, arguments);
+ };
+
+/**
+ * Unblock an user by identifier.
+ *
+ * @method    deleteByIdentifier
+ * @memberOf  module:management.UserBlocksManager.prototype
+ *
+ * @example
+ * management.userBlocks.deleteByIdentifier({ identifier: USER_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // User unblocked.
+ * });
+ *
+ *
+ * @param   {Object}    params              The user data object..
+ * @param   {String}    params.identifier   The user identifier, any of: username, phone_number, email.
+ * @param   {Function}  [cb]                Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+ UserBlocksManager.prototype.deleteByIdentifier = function(params) {
+   if (typeof params !== 'object' || typeof params.identifier !== 'string') {
+     throw new ArgumentError('You must provide an user identifier for the deleteByIdentifier method');
+   }
+
+   return this.userBlocksByIdentifier.delete.apply(this.userBlocksByIdentifier, arguments);
+ };
+
+ module.exports = UserBlocksManager;
diff --git a/src/management/index.js b/src/management/index.js
index 33a544ccc..b687a7e0b 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -12,6 +12,7 @@ var ClientsManager = require('./ClientsManager');
 var ClientGrantsManager = require('./ClientGrantsManager');
 var GrantsManager = require('./GrantsManager');
 var UsersManager = require('./UsersManager');
+var UserBlocksManager = require('./UserBlocksManager');
 var ConnectionsManager = require('./ConnectionsManager');
 var BlacklistedTokensManager = require('./BlacklistedTokensManager');
 var RulesManager = require('./RulesManager');
@@ -169,6 +170,14 @@ var ManagementClient = function(options) {
    */
   this.users = new UsersManager(managerOptions);
 
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * user-blocks endpoint.
+   *
+   * @type {UserBlocksManager}
+   */
+  this.userBlocks = new UserBlocksManager(managerOptions);
+
   /**
    * Simple abstraction for performing CRUD operations on the
    * guardian endpoint.
@@ -1502,6 +1511,98 @@ utils.wrapPropertyMethod(
   'users.regenerateRecoveryCode'
 );
 
+/**
+ * Get user blocks by its id.
+ *
+ * @method    getUserBlocks
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.getUserBlocks({ id: USER_ID }, function (err, blocks) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(blocks);
+ * });
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getUserBlocks', 'userBlocks.get');
+
+/**
+ * Unblock an user by its id.
+ *
+ * @method    unblockUser
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.unblockUser({ id: USER_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // User unblocked.
+ * });
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'unblockUser', 'userBlocks.delete');
+
+/**
+ * Get user blocks by its identifier.
+ *
+ * @method    getUserBlocksByIdentifier
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.getUserBlocksByIdentifier({ identifier: USER_ID }, function (err, blocks) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(blocks);
+ * });
+ *
+ * @param   {Object}    params              The user data object..
+ * @param   {String}    params.identifier   The user identifier, any of: username, phone_number, email.
+ * @param   {Function}  [cb]                Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getUserBlocksByIdentifier', 'userBlocks.getByIdentifier');
+
+/**
+ * Unblock an user by its id.
+ *
+ * @method    unblockUser
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.unblockUserByIdentifier({ identifier: USER_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // User unblocked.
+ * });
+ *
+ * @param   {Object}    params              The user data object..
+ * @param   {String}    params.identifier   The user identifier, any of: username, phone_number, email.
+ * @param   {Function}  [cb]                Callback function
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'unblockUserByIdentifier', 'userBlocks.deleteByIdentifier');
+
 /**
  * Get a single Guardian enrollment.
  *


Test patch:
diff --git a/test/management/management-client.tests.js b/test/management/management-client.tests.js
index 4b0f92fdd..64928b492 100644
--- a/test/management/management-client.tests.js
+++ b/test/management/management-client.tests.js
@@ -7,6 +7,7 @@ var ManagementClient = require('../../src/management');
 
 var ArgumentError = require('rest-facade').ArgumentError;
 var UsersManager = require('../../src/management/UsersManager');
+var UserBlocksManager = require('../../src/management/UserBlocksManager');
 var BlacklistedTokensManager = require('../../src/management/BlacklistedTokensManager');
 var ClientsManager = require('../../src/management/ClientsManager');
 var ClientGrantsManager = require('../../src/management/ClientGrantsManager');
@@ -101,6 +102,10 @@ describe('ManagementClient', function() {
         property: 'users',
         cls: UsersManager
       },
+      UserBlocksManager: {
+        property: 'userBlocks',
+        cls: UserBlocksManager
+      },
       BlacklistedTokensManager: {
         property: 'blacklistedTokens',
         cls: BlacklistedTokensManager
@@ -760,7 +765,11 @@ describe('ManagementClient', function() {
       'deleteUserMultifcator',
       'setRulesConfig',
       'getRulesConfigs',
-      'deleteRulesConfig'
+      'deleteRulesConfig',
+      'getUserBlocks',
+      'unblockUser',
+      'getUserBlocksByIdentifier',
+      'unblockUserByIdentifier'
     ];
 
     before(function() {
diff --git a/test/management/user-blocks.tests.js b/test/management/user-blocks.tests.js
new file mode 100644
index 000000000..0db1b37ce
--- /dev/null
+++ b/test/management/user-blocks.tests.js
@@ -0,0 +1,352 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenants.auth0.com';
+
+var UserBlocksManager = require(SRC_DIR + '/management/UserBlocksManager');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+describe('UserBlocksManager', function() {
+  before(function() {
+    this.token = 'TOKEN';
+    this.userBlocks = new UserBlocksManager({
+      headers: { authorization: 'Bearer ' + this.token },
+      baseUrl: API_URL
+    });
+  });
+
+  describe('instance', function() {
+    var methods = ['get', 'delete', 'getByIdentifier', 'deleteByIdentifier'];
+
+    methods.forEach(function(method) {
+      it('should have a ' + method + ' method', function() {
+        expect(this.userBlocks[method]).to.exist.to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+  describe('#constructor', function() {
+    it('should error when no options are provided', function() {
+      expect(UserBlocksManager).to.throw(ArgumentError, 'Must provide manager options');
+    });
+
+    it('should throw an error when no base URL is provided', function() {
+      var manager = UserBlocksManager.bind(null, {});
+
+      expect(manager).to.throw(ArgumentError, 'Must provide a base URL for the API');
+    });
+
+    it('should throw an error when the base URL is invalid', function() {
+      var manager = UserBlocksManager.bind(null, { baseUrl: '' });
+
+      expect(manager).to.throw(ArgumentError, 'The provided base URL is invalid');
+    });
+  });
+
+  describe('#get', function() {
+    var id = 'USER_5';
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/user-blocks/' + id)
+        .reply(200);
+    });
+
+    afterEach(function() {
+      nock.cleanAll();
+    })
+
+    it('should throw an error when no id is provided', function() {
+      var userBlocks = this.userBlocks
+
+      expect(function() {
+        userBlocks.get({});
+      }).to.throw(ArgumentError, 'You must provide an user id for the get method');
+    });
+
+    it('should accept a callback', function(done) {
+      this.userBlocks.get({ id }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.userBlocks.get({ id }).then(done.bind(null, null));
+    });
+
+    it('should perform a get request to /user-blocks/' + id, function(done) {
+      var request = this.request;
+
+      this.userBlocks.get({ id })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      nock(API_URL)
+        .get('/user-blocks/' + id)
+        .reply(500);
+
+      this.userBlocks.get({ id })
+        .catch(function(err) {
+          expect(err).to.exist;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/user-blocks/' + id)
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.userBlocks.get({ id })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+  describe('#delete', function() {
+    var id = 'USER_5';
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .delete('/user-blocks/' + id)
+        .reply(200);
+    });
+
+    afterEach(function() {
+      nock.cleanAll();
+    })
+
+    it('should throw an error when no id is provided', function() {
+      var userBlocks = this.userBlocks
+
+      expect(function() {
+        userBlocks.delete({});
+      }).to.throw(ArgumentError, 'You must provide an user id for the delete method');
+    });
+
+    it('should accept a callback', function(done) {
+      this.userBlocks.delete({ id }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.userBlocks.delete({ id }).then(done.bind(null, null));
+    });
+
+    it('should perform a delete request to /user-blocks/' + id, function(done) {
+      var request = this.request;
+
+      this.userBlocks.delete({ id })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      nock(API_URL)
+        .delete('/user-blocks/' + id)
+        .reply(500);
+
+      this.userBlocks.delete({ id })
+        .catch(function(err) {
+          expect(err).to.exist;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/user-blocks/' + id)
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.userBlocks.delete({ id })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+  describe('#getByIdentifier', function() {
+    var identifier = 'USER_5';
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/user-blocks')
+        .query({ identifier })
+        .reply(200);
+    });
+
+    afterEach(function() {
+      nock.cleanAll();
+    })
+
+    it('should throw an error when no identifier is provided', function() {
+      var userBlocks = this.userBlocks
+
+      expect(function() {
+        userBlocks.getByIdentifier({});
+      }).to.throw(ArgumentError, 'You must provide an user identifier for the getByIdentifier method');
+    });
+
+    it('should accept a callback', function(done) {
+      this.userBlocks.getByIdentifier({ identifier }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.userBlocks.getByIdentifier({ identifier }).then(done.bind(null, null));
+    });
+
+    it('should perform a get request to /user-blocks', function(done) {
+      var request = this.request;
+
+      this.userBlocks.getByIdentifier({ identifier })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      nock(API_URL)
+        .get('/user-blocks')
+        .query({ identifier })
+        .reply(500);
+
+      this.userBlocks.getByIdentifier({ identifier })
+        .catch(function(err) {
+          expect(err).to.exist;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/user-blocks')
+        .query({ identifier })
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.userBlocks.getByIdentifier({ identifier })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+  describe('#deleteByIdentifier', function() {
+    var identifier = 'USER_5';
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .delete('/user-blocks')
+        .query({ identifier })
+        .reply(200);
+    });
+
+    afterEach(function() {
+      nock.cleanAll();
+    })
+
+    it('should throw an error when no identifier is provided', function() {
+      var userBlocks = this.userBlocks
+
+      expect(function() {
+        userBlocks.deleteByIdentifier({});
+      }).to.throw(ArgumentError, 'You must provide an user identifier for the deleteByIdentifier method');
+    });
+
+    it('should accept a callback', function(done) {
+      this.userBlocks.deleteByIdentifier({ identifier }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.userBlocks.deleteByIdentifier({ identifier }).then(done.bind(null, null));
+    });
+
+    it('should perform a delete request to /user-blocks', function(done) {
+      var request = this.request;
+
+      this.userBlocks.deleteByIdentifier({ identifier })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      nock(API_URL)
+        .delete('/user-blocks')
+        .query({ identifier })
+        .reply(500);
+
+      this.userBlocks.deleteByIdentifier({ identifier })
+        .catch(function(err) {
+          expect(err).to.exist;
+
+          done();
+        })
+        .catch(done);
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/user-blocks')
+        .query({ identifier })
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.userBlocks.deleteByIdentifier({ identifier })
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+});