From 0982ca4197ad39646074d356e6f7f80fa9f9d41f Mon Sep 17 00:00:00 2001 From: jbauth0 <49204697+jbauth0@users.noreply.github.com> Date: Thu, 8 Aug 2019 07:19:19 -0700 Subject: [PATCH] Improve JobManager (get errors + parse error message when importing users) (#407) * Add support for retrieving errors from import jobs, Add support for parsing error message from the json body of JobsManager.importUsers when present * Try to improve code coverage --- src/management/JobsManager.js | 59 +++++++++++++++ test/management/jobs.tests.js | 138 ++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/src/management/JobsManager.js b/src/management/JobsManager.js index 3c244f290..d717ed9d6 100644 --- a/src/management/JobsManager.js +++ b/src/management/JobsManager.js @@ -58,6 +58,19 @@ var JobsManager = function(options) { ); this.jobs = new RetryRestClient(auth0RestClient, options.retry); + /** + * Provides an abstraction layer for consuming the + * {@link https://auth0.com/docs/api/v2#!/Jobs/:id/errors Errors endpoint}. + * + * @type {external:RestClient} + */ + var jobErrorsRestClient = new Auth0RestClient( + options.baseUrl + '/jobs/:id/errors', + clientOptions, + options.tokenProvider + ); + this.jobErrors = new RetryRestClient(jobErrorsRestClient, options.retry); + /** * Provides an abstraction layer for consuming the * {@link https://auth0.com/docs/api/v2#!/Jobs/post_users_exports Create job to export users endpoint} @@ -189,6 +202,13 @@ JobsManager.prototype.importUsers = function(data, cb) { error.status = res.statusCode; error.method = method; error.text = res.text; + try { + if (!error.text && res.body) { + error.text = JSON.parse(res.body).message; + } + } catch (ex) { + // Ignore the error. + } reject(error); } resolve(res); @@ -264,6 +284,45 @@ JobsManager.prototype.exportUsers = function(data, cb) { return this.usersExports.create(data); }; +/** + * Given a job ID, retrieve the failed/errored items + * + * @method get + * @memberOf module:management.JobsManager.prototype + * + * @example + * var params = { + * id: '{JOB_ID}' + * }; + * + * management.jobs.errors(params, function (err, job) { + * if (err) { + * // Handle error. + * } + * + * // Retrieved job. + * console.log(job); + * }); + * + * @param {Object} params Job parameters. + * @param {String} params.id Job ID. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +JobsManager.prototype.errors = function(params, cb) { + if (!params.id || typeof params.id !== 'string') { + throw new ArgumentError('The id parameter must be a valid job id'); + } + + if (cb && cb instanceof Function) { + return this.jobErrors.get(params, cb); + } + + // Return a promise. + return this.jobErrors.get(params); +}; + /** * Send a verification email to a user. * diff --git a/test/management/jobs.tests.js b/test/management/jobs.tests.js index b8b7cb98c..5bccf6c51 100644 --- a/test/management/jobs.tests.js +++ b/test/management/jobs.tests.js @@ -87,6 +87,15 @@ describe('JobsManager', function() { }); }); + it('should throw an ArgumentError if an invalid id is passed', function(done) { + try { + this.jobs.errors({ id: 12345 }, function() {}); + } catch (err) { + expect(err).to.exist; + done(); + } + }); + it('should pass the body of the response to the "then" handler', function(done) { nock.cleanAll(); @@ -148,6 +157,110 @@ describe('JobsManager', function() { }); }); + // Error retrieval tests + describe('#errors', function() { + beforeEach(function() { + this.request = nock(API_URL) + .get('/jobs/' + this.id + '/errors') + .reply(200); + }); + + it('should accept a callback', function(done) { + this.jobs.errors({ id: this.id }, function() { + done(); + }); + }); + + it('should return a promise if no callback is given', function(done) { + this.jobs + .errors({ id: this.id }) + .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('/jobs/' + this.id + '/errors') + .reply(500); + + this.jobs.errors({ id: this.id }).catch(function(err) { + expect(err).to.exist; + done(); + }); + }); + + it('should throw an ArgumentError if an invalid id is passed', function(done) { + try { + this.jobs.errors({ id: null }, function() {}); + } catch (err) { + expect(err).to.exist; + done(); + } + }); + + it('should pass the body of the response to the "then" handler', function(done) { + nock.cleanAll(); + + var data = [{ test: true }]; + var request = nock(API_URL) + .get('/jobs/' + this.id + '/errors') + .reply(200, data); + + this.jobs.errors({ id: this.id }).then(function(blacklistedTokens) { + expect(blacklistedTokens).to.be.an.instanceOf(Array); + + expect(blacklistedTokens.length).to.equal(data.length); + + expect(blacklistedTokens[0].test).to.equal(data[0].test); + + done(); + }); + }); + + it('should perform a GET request to /api/v2/jobs/:id/errors', function(done) { + var request = this.request; + + this.jobs.errors({ id: this.id }).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('/jobs/' + this.id + '/errors') + .matchHeader('Authorization', 'Bearer ' + token) + .reply(200); + + this.jobs.errors({ id: this.id }).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('/jobs/' + this.id) + .query({ + include_fields: true, + fields: 'test' + }) + .reply(200); + + this.jobs.get({ id: this.id, include_fields: true, fields: 'test' }).then(function() { + expect(request.isDone()).to.be.true; + done(); + }); + }); + }); + const usersFilePath = path.join(__dirname, '../data/users.json'); describe('#importUsers', function() { @@ -203,6 +316,31 @@ describe('JobsManager', function() { }); }); + it('should pass rest-api json error messages to the promise catch handler', function(done) { + nock.cleanAll(); + + var request = nock(API_URL) + .post('/jobs/users-imports') + .reply((uri, requestBody) => { + return [ + 429, + { + statusCode: 429, + error: 'Too Many Requests', + message: 'There are 4 active import users jobs' + } + ]; + }); + + this.jobs.importUsers(data).catch(function(err) { + expect(err.message).to.equal( + 'cannot POST https://tenant.auth0.com/jobs/users-imports (429)' + ); + expect(err.text).to.equal('There are 4 active import users jobs'); + done(); + }); + }); + it('should perform a POST request to /api/v2/jobs/users-imports', function(done) { var request = this.request;