Repository: auth0/node-auth0
Pull request #518 (2020-07-24 11:29:38)

Added support for Log Streams
### Changes

_NOTE: this is a duplicate of #511_ 

The new class that was added is called `LogStreamsManager` and it's adding the following endpoints:

GET /api/v2/log-streams
GET /api/v2/log-streams/{id}
POST /api/v2/log-streams
PATCH /api/v2/log-streams/{id}
DELETE /api/v2/log-streams/{id}

### References

The relevant operations for Log Streams are described [here](https://auth0.com/docs/api/management/v2#!/Log_Streams/get_log_streams)

_Also a correction_: the relevant docs [here](https://auth0.com/docs/api/management/v2#!/Log_Streams/post_log_streams) for creating a stream have a typo, the `httpAuthorizationHeader` field should be `httpAuthorization` but I don't have access to the api2 repo to open a proper issue there.

### Testing

Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. 

- [x] This change adds unit test coverage
- [ ] This change adds integration test coverage

### Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [x] All existing and new tests complete without errors



Patch:
diff --git a/src/management/LogStreamsManager.js b/src/management/LogStreamsManager.js
new file mode 100644
index 000000000..43cae1f4c
--- /dev/null
+++ b/src/management/LogStreamsManager.js
@@ -0,0 +1,179 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var utils = require('../utils');
+var Auth0RestClient = require('../Auth0RestClient');
+var RetryRestClient = require('../RetryRestClient');
+
+/**
+ * @class LogStreamsManager
+ * The logStreams class provides a simple abstraction for performing CRUD operations
+ * on Auth0 Log Streams.
+ * @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 LogStreamsManager = function(options) {
+  if (options === null || typeof options !== 'object') {
+    throw new ArgumentError('Must provide client 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');
+  }
+
+  /**
+   * Options object for the Rest Client instance.
+   *
+   * @type {Object}
+   */
+  var clientOptions = {
+    headers: options.headers,
+    query: { repeatParams: false }
+  };
+
+  /**
+   * Provides an abstraction layer for performing CRUD operations on
+   * {@link https://auth0.com/docs/api/management/v2#!/Log_Streams Auth0
+   *  Log Streams}.
+   *
+   * @type {external:RestClient}
+   */
+  var auth0RestClient = new Auth0RestClient(
+    options.baseUrl + '/log-streams/:id ',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.resource = new RetryRestClient(auth0RestClient, options.retry);
+};
+
+/**
+ * Get all Log Streams.
+ *
+ * @method    getAll
+ * @memberOf  module:management.LogStreamsManager.prototype
+ *
+ * @example
+ *
+ * management.logStreams.getAll(function (err, logStreams) {
+ *   console.log(logStreams.length);
+ * });
+ *
+ * @param   {Function}  [cb]                    Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(LogStreamsManager, 'getAll', 'resource.getAll');
+
+/**
+ * Get an Auth0 Log Streams.
+ *
+ * @method    get
+ * @memberOf  module:management.LogStreamsManager.prototype
+ *
+ * @example
+ * management.logStreams.get({ id: LOG_STREAM_ID }, function (err, logStream) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(logStream);
+ * });
+ *
+ * @param   {Object}    params          Log Stream parameters.
+ * @param   {String}    params.id       Log Stream ID.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(LogStreamsManager, 'get', 'resource.get');
+
+/**
+ * Create an Auth0 Log Stream.
+ *
+ * @method    create
+ * @memberOf  module:management.LogStreamsManager.prototype
+ *
+ * @example
+ * management.logStreams.create(data, function (err, log) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(log);
+ * });
+ *
+ * @param   {Object}    data          Log Stream data.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(LogStreamsManager, 'create', 'resource.create');
+
+/**
+ * Update an Auth0 Log Streams.
+ *
+ * @method    update
+ * @memberOf  module:management.LogStreamsManager.prototype
+ *
+ * @example
+ * var data = { name: 'New name' };
+ * var params = { id: LOG_STREAM_ID };
+ *
+ * // Using auth0 instance.
+ * management.updateLogStream(params, data, function (err, logStream) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(logStream.name);  // 'New name'
+ * });
+ *
+ * // Using the logStreams manager directly.
+ * management.logStreams.update(params, data, function (err, logStream) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(logStream.name);
+ * });
+ *
+ * @param   {Object}    params          Log Stream parameters.
+ * @param   {String}    params.id       Log Stream ID.
+ * @param   {Object}    data            Updated Log Stream data.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(LogStreamsManager, 'update', 'resource.patch');
+
+/**
+ * Delete an Auth0 Log Streams.
+ *
+ * @method    delete
+ * @memberOf  module:management.LogStreamsManager.prototype
+ *
+ * @example
+ * management.logStreams.delete({ id: LOG_STREAM_ID }, function (err, log) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(log);
+ * });
+ *
+ * @param   {Object}    params          Log Stream parameters.
+ * @param   {String}    params.id       Log Stream ID.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(LogStreamsManager, 'delete', 'resource.delete');
+
+module.exports = LogStreamsManager;
diff --git a/src/management/index.js b/src/management/index.js
index 77e6327d6..f06da9331 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -23,6 +23,7 @@ var TenantManager = require('./TenantManager');
 var JobsManager = require('./JobsManager');
 var TicketsManager = require('./TicketsManager');
 var LogsManager = require('./LogsManager');
+var LogStreamsManager = require('./LogStreamsManager');
 var ResourceServersManager = require('./ResourceServersManager');
 var ManagementTokenProvider = require('./ManagementTokenProvider');
 var RulesConfigsManager = require('./RulesConfigsManager');
@@ -282,6 +283,13 @@ var ManagementClient = function(options) {
    */
   this.logs = new LogsManager(managerOptions);
 
+  /**
+   * Log Streams manager.
+   *
+   * @type {LogStreamsManager}
+   */
+  this.logStreams = new LogStreamsManager(managerOptions);
+
   /**
    * Simple abstraction for performing CRUD operations on the
    * resource servers endpoint.
@@ -2246,6 +2254,118 @@ utils.wrapPropertyMethod(ManagementClient, 'getLog', 'logs.get');
  */
 utils.wrapPropertyMethod(ManagementClient, 'getLogs', 'logs.getAll');
 
+/**
+ * Get all Log Streams.
+ *
+ * @method    getLogStreams
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ *
+ *
+ * management.getLogStreams( function (err, logStreams) {
+ *   console.log(logStreams.length);
+ * });
+ *
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getLogStreams', 'logStreams.getAll');
+
+/**
+ * Create a new Log Stream.
+ *
+ * @method    createLogStream
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.createLogStream(data, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // Log Stream created.
+ * });
+ *
+ * @param   {Object}    data          Log Stream data.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createLogStream', 'logStreams.create');
+
+/**
+ * Get an Auth0 Log Stream.
+ *
+ * @method    getLogStream
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.getLogStream({ id: LOG_STREAM_ID }, function (err, logStream) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(logStream);
+ * });
+ *
+ * @param   {Object}    params        Log Stream parameters.
+ * @param   {String}    params.id     Log Stream ID.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getLogStream', 'logStreams.get');
+
+/**
+ * Delete an existing Log Stream.
+ *
+ * @method    deleteLogStream
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.deleteLogStream({ id: LOG_STREAM_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // Log Stream deleted.
+ * });
+ *
+ * @param   {Object}    params        Log Stream parameters.
+ * @param   {String}    params.id     Log Stream ID.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteLogStream', 'logStreams.delete');
+
+/**
+ * Update an existing Log Stream.
+ *
+ * @method    updateLogStream
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * var params = { id: LOG_STREAM_ID };
+ * var data = { name: 'my-log-stream'};
+ * management.updateLogStream(params, data, function (err, logStream) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(logStream.name); // 'my-log-stream'.
+ * });
+ *
+ * @param   {Object}    params        Rule parameters.
+ * @param   {String}    params.id     Rule ID.
+ * @param   {Object}    data          Updated rule data.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateLogStream', 'logStreams.update');
+
 /**
  * Create a new resource server.
  *


Test patch:
diff --git a/test/management/log-streams.tests.js b/test/management/log-streams.tests.js
new file mode 100644
index 000000000..a0fcf8681
--- /dev/null
+++ b/test/management/log-streams.tests.js
@@ -0,0 +1,415 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenant.auth0.com';
+
+var LogStreamsManager = require(SRC_DIR + '/management/LogStreamsManager');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+describe('LogStreamsManager', function() {
+  before(function() {
+    this.token = 'TOKEN';
+    this.logStreams = new LogStreamsManager({
+      headers: { authorization: 'Bearer ' + this.token },
+      baseUrl: API_URL
+    });
+  });
+
+  describe('instance', function() {
+    var methods = ['getAll', 'get', 'create', 'update', 'delete'];
+
+    methods.forEach(function(method) {
+      it('should have a ' + method + ' method', function() {
+        expect(this.logStreams[method]).to.exist.to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+  describe('#constructor', function() {
+    it('should error when no options are provided', function() {
+      expect(LogStreamsManager).to.throw(ArgumentError, 'Must provide client options');
+    });
+
+    it('should throw an error when no base URL is provided', function() {
+      var client = LogStreamsManager.bind(null, {});
+
+      expect(client).to.throw(ArgumentError, 'Must provide a base URL for the API');
+    });
+
+    it('should throw an error when the base URL is invalid', function() {
+      var client = LogStreamsManager.bind(null, { baseUrl: '' });
+
+      expect(client).to.throw(ArgumentError, 'The provided base URL is invalid');
+    });
+  });
+
+  describe('#getAll', function() {
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/log-streams')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.logStreams.getAll(function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.logStreams
+        .getAll()
+        .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('/log-streams')
+        .reply(500);
+
+      this.logStreams.getAll().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 = [{ test: true }];
+      var request = nock(API_URL)
+        .get('/log-streams')
+        .reply(200, data);
+
+      this.logStreams.getAll().then(function(logStreams) {
+        expect(logStreams).to.be.an.instanceOf(Array);
+
+        expect(logStreams.length).to.equal(data.length);
+
+        expect(logStreams[0].test).to.equal(data[0].test);
+
+        done();
+      });
+    });
+
+    it('should perform a GET request to /api/v2/log-streams', function(done) {
+      var request = this.request;
+
+      this.logStreams.getAll().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('/log-streams')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.logStreams.getAll().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+  });
+
+  describe('#get', function() {
+    var params = { id: 5 };
+    var data = {
+      id: params.id,
+      name: 'Test log'
+    };
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/log-streams/' + data.id)
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.logStreams.get(params, function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.logStreams
+        .get(params)
+        .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('/log-streams/' + params.id)
+        .reply(500);
+
+      this.logStreams.get().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 request = nock(API_URL)
+        .get('/log-streams/' + params.id)
+        .reply(200, data);
+
+      this.logStreams.get(params).then(function(log) {
+        expect(log.id).to.equal(data.id);
+
+        done();
+      });
+    });
+
+    it('should perform a GET request to /api/v2/log-streams/:id', function(done) {
+      var request = this.request;
+
+      this.logStreams.get(params).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('/log-streams')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.logStreams.getAll().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('/log-streams')
+        .query({
+          include_fields: true,
+          fields: 'test'
+        })
+        .reply(200);
+
+      this.logStreams.getAll({ include_fields: true, fields: 'test' }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
+  describe('#create', function() {
+    var data = {
+      name: 'Test log stream'
+    };
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .post('/log-streams')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.logStreams.create(data, function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.logStreams
+        .create(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)
+        .post('/log-streams')
+        .reply(500);
+
+      this.logStreams.create(data).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+
+    it('should perform a POST request to /api/v2/log-streams', function(done) {
+      var request = this.request;
+
+      this.logStreams.create(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)
+        .post('/log-streams', data)
+        .reply(200);
+
+      this.logStreams.create(data).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)
+        .post('/log-streams')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.logStreams.create(data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
+  describe('#update', function() {
+    beforeEach(function() {
+      this.data = { id: 5 };
+
+      this.request = nock(API_URL)
+        .patch('/log-streams/' + this.data.id)
+        .reply(200, this.data);
+    });
+
+    it('should accept a callback', function(done) {
+      this.logStreams.update({ id: 5 }, {}, done.bind(null, null));
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.logStreams
+        .update({ id: 5 }, {})
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should perform a PATCH request to /api/v2/log-streams/5', function(done) {
+      var request = this.request;
+
+      this.logStreams.update({ id: 5 }, {}).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should include the new data in the body of the request', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .patch('/log-streams/' + this.data.id, this.data)
+        .reply(200);
+
+      this.logStreams.update({ id: 5 }, this.data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .patch('/log-streams/' + this.data.id)
+        .reply(500);
+
+      this.logStreams.update({ id: this.data.id }, this.data).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+  });
+
+  describe('#delete', function() {
+    var id = 5;
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .delete('/log-streams/' + id)
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.logStreams.delete({ id: id }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.logStreams.delete({ id: id }).then(done.bind(null, null));
+    });
+
+    it('should perform a delete request to /log-streams/' + id, function(done) {
+      var request = this.request;
+
+      this.logStreams.delete({ id: id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/log-streams/' + id)
+        .reply(500);
+
+      this.logStreams.delete({ id: id }).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/log-streams/' + id)
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.logStreams.delete({ id: id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+});