Repository: auth0/node-auth0
Pull request #503 (2020-06-26 18:36:33)

feat(migrations): adds migrations manager
### Changes

- Adds support for the `/migrations` endpoint which supports `GET` and `PATCH` operations
- Adds `MigrationsManager` to handle the new endpoint
- The new endpoint provides a mechanism to enable/disable ongoing migrations for the tenant.

### Testing

Added `migrations.test.js`



Patch:
diff --git a/src/management/MigrationsManager.js b/src/management/MigrationsManager.js
new file mode 100644
index 000000000..b051deecb
--- /dev/null
+++ b/src/management/MigrationsManager.js
@@ -0,0 +1,108 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var Auth0RestClient = require('../Auth0RestClient');
+var RetryRestClient = require('../RetryRestClient');
+
+/**
+ * @class
+ * Abstracts interaction with the migrations 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 MigrationsManager = 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 }
+  };
+
+  /**
+   * Provides an abstraction layer for consuming the migrations endpoint
+   *
+   * @type {external:RestClient}
+   */
+  var auth0RestClient = new Auth0RestClient(
+    options.baseUrl + '/migrations',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.resource = new RetryRestClient(auth0RestClient, options.retry);
+};
+
+/**
+ * Update the tenant migrations.
+ *
+ * @method    updateMigrations
+ * @memberOf  module:management.MigrationsManager.prototype
+ *
+ * @example
+ * management.migrations.updateMigrations(data, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ * });
+ *
+ * @param   {Object}    data  The tenant migrations to be updated
+ * @param   {Object}    data.flags  The tenant migrations flags to be updated
+ * @param   {Function}  [cb]  Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+MigrationsManager.prototype.updateMigrations = function(data, cb) {
+  if (cb && cb instanceof Function) {
+    return this.resource.patch({}, data, cb);
+  }
+
+  // Return a promise.
+  return this.resource.patch({}, data);
+};
+
+/**
+ * Get the tenant migrations.
+ *
+ * @method    getMigrations
+ * @memberOf  module:management.MigrationsManager.prototype
+ *
+ * @example
+ * management.migrations.getMigrations(function (err, migrations) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(migrations.flags);
+ * });
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+MigrationsManager.prototype.getMigrations = function(data, cb) {
+  if (data instanceof Function && !cb) {
+    cb = data;
+    data = {};
+  }
+  if (cb && cb instanceof Function) {
+    return this.resource.get(data, cb);
+  }
+
+  // Return a promise.
+  return this.resource.get(data);
+};
+
+module.exports = MigrationsManager;
diff --git a/src/management/index.js b/src/management/index.js
index c329fe321..66706a3b4 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -32,6 +32,7 @@ var CustomDomainsManager = require('./CustomDomainsManager');
 var RolesManager = require('./RolesManager');
 var HooksManager = require('./HooksManager');
 var BrandingManager = require('./BrandingManager');
+var MigrationsManager = require('./MigrationsManager');
 
 var BASE_URL_FORMAT = 'https://%s/api/v2';
 var MANAGEMENT_API_AUD_FORMAT = 'https://%s/api/v2/';
@@ -327,6 +328,13 @@ var ManagementClient = function(options) {
    * @type {HooksManager}
    */
   this.branding = new BrandingManager(managerOptions);
+
+  /**
+   * ManagementClient migrations manager.
+   *
+   * @type {MigrationsManager}
+   */
+  this.migrations = new MigrationsManager(managerOptions);
 };
 
 /**
@@ -3344,4 +3352,50 @@ utils.wrapPropertyMethod(ManagementClient, 'updateBrandingSettings', 'branding.u
  */
 utils.wrapPropertyMethod(ManagementClient, 'getBrandingSettings', 'branding.getSettings');
 
+/**
+ * Update the tenant migrations.
+ *
+ * @method    updateMigrations
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * data = { flags: { migration: true } };
+ * management.updateMigrations(data, function (err, migrations) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ * // Updated migrations flags
+ *    console.log(migrations.flags);
+ * });
+ *
+ * @param   {Object}    data              Updated migrations data.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return    {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateMigrations', 'migrations.updateMigrations');
+
+/**
+ * Get migrations flags
+ *
+ * @method    getMigrations
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.getMigrations(function (err, migrations) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ * // Migration flags
+ *    console.log(migrations.flags);
+ * });
+ *
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return    {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getMigrations', 'migrations.getMigrations');
+
 module.exports = ManagementClient;


Test patch:
diff --git a/test/management/migrations.tests.js b/test/management/migrations.tests.js
new file mode 100644
index 000000000..5a6db82d9
--- /dev/null
+++ b/test/management/migrations.tests.js
@@ -0,0 +1,230 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenants.auth0.com';
+
+var MigrationsManager = require(SRC_DIR + '/management/MigrationsManager');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+describe('MigrationsManager', function() {
+  before(function() {
+    this.token = 'TOKEN';
+    this.migrations = new MigrationsManager({
+      headers: { authorization: 'Bearer ' + this.token },
+      baseUrl: API_URL
+    });
+  });
+
+  describe('instance', function() {
+    var methods = ['updateMigrations', 'getMigrations'];
+
+    methods.forEach(function(method) {
+      it('should have a ' + method + ' method', function() {
+        expect(this.migrations[method]).to.exist.to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+  describe('#constructor', function() {
+    it('should error when no options are provided', function() {
+      expect(MigrationsManager).to.throw(ArgumentError, 'Must provide manager options');
+    });
+
+    it('should throw an error when no base URL is provided', function() {
+      var manager = MigrationsManager.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 = MigrationsManager.bind(null, { baseUrl: '' });
+
+      expect(manager).to.throw(ArgumentError, 'The provided base URL is invalid');
+    });
+  });
+
+  describe('#getMigrations', function() {
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/migrations')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.migrations.getMigrations(function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.migrations
+        .getMigrations()
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/migrations')
+        .reply(500);
+
+      this.migrations.getMigrations().catch(function(err) {
+        expect(err).to.exist;
+        done();
+      });
+    });
+
+    it('should pass the body of the response to the "then" handler', function(done) {
+      nock.cleanAll();
+
+      var data = { flags: { migration_flag: true } };
+      var request = nock(API_URL)
+        .get('/migrations')
+        .reply(200, data);
+
+      this.migrations.getMigrations().then(function(migrations) {
+        expect(migrations).to.be.an('object');
+
+        expect(migrations).to.have.nested.property('flags.migration_flag', true);
+
+        done();
+      });
+    });
+
+    it('should perform a GET request to /api/v2/migrations', function(done) {
+      var request = this.request;
+
+      this.migrations.getMigrations().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+
+    it('should include the token in the Authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/migrations')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.migrations.getMigrations().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+
+    it('should pass the parameters in the query-string', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/migrations')
+        .query({
+          any: 'test'
+        })
+        .reply(200);
+
+      this.migrations.getMigrations({ any: 'test' }).then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+  });
+
+  describe('#updateMigrations', function() {
+    var data = {
+      flags: {
+        migration: false
+      }
+    };
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .patch('/migrations')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.migrations.updateMigrations(data, function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.migrations
+        .updateMigrations(data)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .patch('/migrations')
+        .reply(500);
+
+      this.migrations.updateMigrations(data).catch(function(err) {
+        expect(err).to.exist;
+        done();
+      });
+    });
+
+    it('should perform a PATCH request to /api/v2migrations', function(done) {
+      var request = this.request;
+
+      this.migrations.updateMigrations(data).then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+
+    it('should pass the data in the body of the request', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .patch('/migrations', data)
+        .reply(200);
+
+      this.migrations.updateMigrations(data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass the body of the response to the "then" handler', function(done) {
+      nock.cleanAll();
+
+      var result = { flags: { migration_flag: true } };
+      var request = nock(API_URL)
+        .patch('/migrations')
+        .reply(200, result);
+
+      this.migrations.updateMigrations(data).then(function(migrations) {
+        expect(migrations).to.be.an('object');
+
+        expect(migrations).to.have.nested.property('flags.migration_flag', true);
+
+        done();
+      });
+    });
+
+    it('should include the token in the Authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .patch('/migrations')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.migrations.updateMigrations(data).then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+  });
+});