Repository: auth0/node-auth0 (ts)

Issue:
 ManagementClient#importUsers returns Promise of IncomingMessage
### Description

`exportUsers()` and `getJob()` returns promise of job object as expected. However, `importUsers()` returns a promise of IncomingMessage object[1]. This seems to be inconsistent.

To wait until the import job is completed, we need to get an ID of the job from the response. To do so, we need to parse the response body like `const job = JSON.parse(resImportUsers.body)`.

It would be great if `importUsers()` returns a promise of job object as `exportUsers()` and `getJob()` do. 

[1] https://github.com/auth0/node-auth0/blob/v2.23.0/src/management/JobsManager.js#L214

### Reproduction

```js
// reproduce.js
const auth0 = require("auth0")
const ManagementClient = auth0.ManagementClient

const client = new ManagementClient({
  domain: "****.auth0.com",
  clientId: "*****",
  clientSecret: "****",
})

;(async () => {
  console.log("--- exportUsers ---")
  const resExportUsers = await client.exportUsers({})
  console.log(resExportUsers) // Job object as expected

  console.log("--- getJob ---")
  const resGetJob = await client.getJob({ id: resExportUsers.id })
  console.log(resGetJob) // Job object as expected

  console.log("--- importUsers ---")
  const resImportUsers = await client.importUsers({
    connection_id: "con_*******",
    users_json: `[{"email":"sample@example.com"}]`,
  })
  console.log(resImportUsers) // IncomingMessage object as not expected
  console.log("-------------------")
  console.log(JSON.parse(resImportUsers.body)) // What we actually need
})()
```


```
$ node reproduce.js
--- exportUsers ---
{ type: 'users_export',
  status: 'pending',
  format: 'csv',
  created_at: '2020-03-04T10:38:58.748Z',
  id: 'job_*****' }
--- getJob ---
{ type: 'users_export',
  status: 'completed',
  format: 'csv',
  created_at: '2020-03-04T10:38:58.748Z',
  id: 'job_*****',
  location:
   'https://user-exports.auth0.com/job_*****/...' }
--- importUsers ---
IncomingMessage {
  _readableState:
  ...
  body:
   '{"type":"users_import","status":"pending","connection_id":"con_*****","connection":"Username-Password-Authentication","created_at":"2020-03-04T10:39:00.504Z","id":"job_*****"}' }
-------------------
{ type: 'users_import',
  status: 'pending',
  connection_id: 'con_*****',
  connection: 'Username-Password-Authentication',
  created_at: '2020-03-04T10:39:00.504Z',
  id: 'job_*****' }
```
### Environment

- **Version of this library used:** `auth0@2.23.0`
- **Other relevant versions (language, server software, OS, browser):** `Node.js v10.15.3`, `macOS 10.14`




Pull request:
JobsManager handling importUsers response payload
Currently TokensManager and UserManager return response data, while Jobs is returning the whole response.  To avoid a breaking change, and make the experience consistent with the Jobs manager, we are adding a new function `importUsersJob` that will extract the data from the response

Follow up to #491 
Closes #467

Patch:
diff --git a/src/management/JobsManager.js b/src/management/JobsManager.js
index 6efa0467f..9c717f991 100644
--- a/src/management/JobsManager.js
+++ b/src/management/JobsManager.js
@@ -199,6 +199,49 @@ JobsManager.prototype.importUsers = function(data, cb) {
   return promise;
 };
 
+/**
+ * Given a path to a file and a connection id, create a new job that imports the
+ * users contained in the file or JSON string and associate them with the given
+ * connection.
+ *
+ * @method   importUsersJob
+ * @memberOf module:management.JobsManager.prototype
+ *
+ * @example
+ * var params = {
+ *   connection_id: '{CONNECTION_ID}',
+ *   users: '{PATH_TO_USERS_FILE}' // or users_json: '{USERS_JSON_STRING}'
+ * };
+ *
+ * management.jobs.importUsers(params, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ * });
+ *
+ * @param   {Object}    data                          Users import data.
+ * @param   {String}    data.connection_id            connection_id of the connection to which users will be imported.
+ * @param   {String}    [data.users]                  Path to the users data file. Either users or users_json is mandatory.
+ * @param   {String}    [data.users_json]             JSON data for the users.
+ * @param   {Boolean}   [data.upsert]                 Whether to update users if they already exist (true) or to ignore them (false).
+ * @param   {Boolean}   [data.send_completion_email]  Whether to send a completion email to all tenant owners when the job is finished (true) or not (false).
+ * @param   {Function}  [cb]                          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+JobsManager.prototype.importUsersJob = function(data, cb) {
+  var promise = this.importUsers(data).then(response => response.data);
+
+  // Don't return a promise if a callback was given.
+  if (cb && cb instanceof Function) {
+    promise.then(cb.bind(null, null)).catch(cb);
+
+    return;
+  }
+
+  return promise;
+};
+
 /**
  * Export all users to a file using a long running job.
  *


Test patch:
diff --git a/test/management/jobs.tests.js b/test/management/jobs.tests.js
index 5bccf6c51..8a6fb6a2a 100644
--- a/test/management/jobs.tests.js
+++ b/test/management/jobs.tests.js
@@ -288,6 +288,31 @@ describe('JobsManager', function() {
         .catch(done.bind(null, null));
     });
 
+    it('should have the payload in response.data', function(done) {
+      nock.cleanAll();
+      var payload = {
+        status: 'pending',
+        type: 'users_import',
+        created_at: '',
+        id: 'job_0000000000000001',
+        connection_id: 'con_0000000000000001',
+        upsert: false,
+        external_id: '',
+        send_completion_email: true
+      };
+      var request = nock(API_URL)
+        .post('/jobs/users-imports')
+        .reply(200, payload);
+
+      this.jobs
+        .importUsers(data)
+        .then(response => {
+          expect(response.data).to.deep.equal(payload);
+          done();
+        })
+        .catch(err => done(err));
+    });
+
     it('should pass request errors to the promise catch handler', function(done) {
       nock.cleanAll();
 
@@ -538,6 +563,58 @@ describe('JobsManager', function() {
     });
   });
 
+  describe('#importUsersJob', function() {
+    var data = {
+      users: usersFilePath,
+      connection_id: 'con_test'
+    };
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .post('/jobs/users-imports')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.jobs.importUsersJob(data, function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.jobs
+        .importUsersJob(data)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should extract data from the response', function(done) {
+      nock.cleanAll();
+
+      var payload = {
+        status: 'pending',
+        type: 'users_import',
+        created_at: '',
+        id: 'job_0000000000000001',
+        connection_id: 'con_0000000000000001',
+        upsert: false,
+        external_id: '',
+        send_completion_email: true
+      };
+      var request = nock(API_URL)
+        .post('/jobs/users-imports')
+        .reply(200, payload);
+
+      this.jobs
+        .importUsersJob(data)
+        .then(response => {
+          expect(response).to.deep.equal(payload);
+          done();
+        })
+        .catch(err => done(err));
+    });
+  });
+
   describe('#exportUsers', function() {
     beforeEach(function() {
       this.request = nock(API_URL)