text: /*!
   * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
   */
  
  import {assert} from '@webex/test-helper-chai';
  import MockWebex from '@webex/test-helper-mock-webex';
  import sinon from 'sinon';
  import {browserOnly, nodeOnly} from '@webex/test-helper-mocha';
  import Logger, {levels} from '@webex/plugin-logger';
  import {WebexHttpError} from '@webex/webex-core';
  
  describe('plugin-logger', () => {
    beforeEach(() => {
      levels.forEach((level) => {
        if (console[level]) {
          sinon.spy(console, level);
        }
      });
    });
  
    afterEach(() => {
      levels.forEach((level) => {
        if (console[level] && console[level].restore) {
          console[level].restore();
        }
      });
    });
  
    let nodeEnv;
  
    beforeEach(() => {
      nodeEnv = process.env.NODE_ENV;
      process.env.NODE_ENV = '';
    });
  
    afterEach(() => {
      process.env.NODE_ENV = nodeEnv;
    });
  
    let webex;
  
    beforeEach(() => {
      webex = new MockWebex({
        children: {
          logger: Logger
        }
      });
    });
  
    const fallbacks = {
      error: ['log'],
      warn: ['error', 'log'],
      info: ['log'],
      debug: ['info', 'log'],
      trace: ['debug', 'info', 'log']
    };
  
    function impl(level) {
      let impls = fallbacks[level];
      let i = level;
  
      if (impls) {
        impls = impls.slice();
        while (!console[i]) {
          i = impls.pop();
        }
      }
  
      return i;
    }
  
    describe('#log()', () => {
      it('prints the specified message to the console', () => {
        webex.config.logger.level = 'trace';
        webex.logger.log('test');
        assert.calledWith(console.log, 'test');
      });
  
      it('stores the specified message in the log buffer', () => {
        webex.logger.log('test');
        assert.lengthOf(webex.logger.buffer, 1);
        assert.isNumber(webex.logger.buffer[0][0]);
        assert.match(webex.logger.buffer[0][1], /test/);
      });
  
      it('prevents the buffer from overflowing', () => {
        webex.config.logger.historyLength = 2;
        webex.logger.log(1);
        assert.lengthOf(webex.logger.buffer, 1);
        webex.logger.log(2);
        assert.lengthOf(webex.logger.buffer, 2);
        webex.logger.log(3);
        assert.lengthOf(webex.logger.buffer, 2);
        assert.equal(webex.logger.buffer[0][1], 2);
        assert.equal(webex.logger.buffer[1][1], 3);
      });
  
      // Node handles custom errors correctly, so this test is browser specific
      browserOnly(it)('prints custom errors in a readable fashion', () => {
        webex.config.logger.level = 'trace';
        const error = new WebexHttpError({
          statusCode: 500,
          body: {
            error: 'Internal Error'
          },
          options: {
            service: '',
            headers: {}
          }
        });
  
        webex.logger.log(error);
        assert.lengthOf(webex.logger.buffer, 1);
        assert.match(console.log.args[0][0], /WebexHttpError/);
      });
  
      it('buffers custom errors in a readable fashion', () => {
        webex.config.logger.level = 'trace';
        const error = new WebexHttpError({
          statusCode: 500,
          body: {
            error: 'Internal Error'
          },
          options: {
            service: '',
            headers: {}
          }
        });
  
        webex.logger.log(error);
        assert.lengthOf(webex.logger.buffer, 1);
        assert.match(webex.logger.buffer[0][1], /WebexHttpError/g);
      });
    });
  
    // We can't manipulate NODE_ENV in karma, tests, so run this chunk only in
    // node
    describe('#shouldPrint()', () => {
      nodeOnly(afterEach)(() => {
        process.env.WEBEX_LOG_LEVEL = undefined;
      });
  
      it('indicates whether or not the desired log should be printed at the current log level', () => {
        /* eslint max-statements: [0] */
        webex.logger.config.level = 'trace';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `trace`');
        assert.isTrue(webex.logger.shouldPrint('warn'), 'it prints `warn` logs when the level is `trace`');
        assert.isTrue(webex.logger.shouldPrint('log'), 'it prints `log` logs when the level is `trace`');
        assert.isTrue(webex.logger.shouldPrint('info'), 'it prints `info` logs when the level is `trace`');
        assert.isTrue(webex.logger.shouldPrint('debug'), 'it prints `debug` logs when the level is `trace`');
        assert.isTrue(webex.logger.shouldPrint('trace'), 'it prints `trace` logs when the level is `trace`');
  
        webex.logger.config.level = 'debug';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `debug`');
        assert.isTrue(webex.logger.shouldPrint('warn'), 'it prints `warn` logs when the level is `debug`');
        assert.isTrue(webex.logger.shouldPrint('log'), 'it prints `log` logs when the level is `debug`');
        assert.isTrue(webex.logger.shouldPrint('info'), 'it prints `info` logs when the level is `debug`');
        assert.isTrue(webex.logger.shouldPrint('debug'), 'it prints `debug` logs when the level is `debug`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `debug`');
  
        webex.logger.config.level = 'info';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `info`');
        assert.isTrue(webex.logger.shouldPrint('warn'), 'it prints `warn` logs when the level is `info`');
        assert.isTrue(webex.logger.shouldPrint('log'), 'it prints `log` logs when the level is `info`');
        assert.isTrue(webex.logger.shouldPrint('info'), 'it prints `info` logs when the level is `info`');
        assert.isFalse(webex.logger.shouldPrint('debug'), 'it does not print `debug` logs when the level is `info`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `info`');
  
        webex.logger.config.level = 'log';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `log`');
        assert.isTrue(webex.logger.shouldPrint('warn'), 'it prints `warn` logs when the level is `log`');
        assert.isTrue(webex.logger.shouldPrint('log'), 'it prints `log` logs when the level is `log`');
        assert.isFalse(webex.logger.shouldPrint('info'), 'it does not print `info` logs when the level is `log`');
        assert.isFalse(webex.logger.shouldPrint('debug'), 'it does not print `debug` logs when the level is `log`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `log`');
  
        webex.logger.config.level = 'warn';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `warn`');
        assert.isTrue(webex.logger.shouldPrint('warn'), 'it prints `warn` logs when the level is `warn`');
        assert.isFalse(webex.logger.shouldPrint('log'), 'it does not print `log` logs when the level is `warn`');
        assert.isFalse(webex.logger.shouldPrint('info'), 'it does not print `info` logs when the level is `warn`');
        assert.isFalse(webex.logger.shouldPrint('debug'), 'it does not print `debug` logs when the level is `warn`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `warn`');
  
        webex.logger.config.level = 'error';
        assert.isTrue(webex.logger.shouldPrint('error'), 'it prints `error` logs when the level is `error`');
        assert.isFalse(webex.logger.shouldPrint('warn'), 'it does not print `warn` logs when the level `error` is ');
        assert.isFalse(webex.logger.shouldPrint('log'), 'it does not print `log` logs when the level is `error`');
        assert.isFalse(webex.logger.shouldPrint('info'), 'it does not print `info` logs when the level is `error`');
        assert.isFalse(webex.logger.shouldPrint('debug'), 'it does not print `debug` logs when the level is `error`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `error`');
  
        webex.logger.config.level = 'silent';
        assert.isFalse(webex.logger.shouldPrint('error'), 'it does not print `error` logs when the level is `silent`');
        assert.isFalse(webex.logger.shouldPrint('warn'), 'it does not print `warn` logs when the level is `silent`');
        assert.isFalse(webex.logger.shouldPrint('log'), 'it does not print `log` logs when the level is `silent`');
        assert.isFalse(webex.logger.shouldPrint('info'), 'it does not print `info` logs when the level is `silent`');
        assert.isFalse(webex.logger.shouldPrint('debug'), 'it does not print `debug` logs when the level is `silent`');
        assert.isFalse(webex.logger.shouldPrint('trace'), 'it does not print `trace` logs when the level is `silent`');
      });
  
      nodeOnly(it)('uses the WEBEX_LOG_LEVEL environment varable to control log level', () => {
        levels.forEach((level) => {
          process.env.WEBEX_LOG_LEVEL = level;
          console[impl(level)].resetHistory();
          webex.logger[level](`test: ${level}`);
          assert.calledOnce(console[impl(level)]);
        });
      });
  
      it('prefers the config specified logger.level', () => {
        levels.forEach((level) => {
          webex.logger.config.level = level;
          console[impl(level)].resetHistory();
          webex.logger[level](`test: ${level}`);
          assert.calledOnce(console[impl(level)]);
        });
      });
  
      nodeOnly(it)('logs at TRACE in test environments', () => {
        console.trace.restore();
        sinon.stub(console, 'trace');
        process.env.NODE_ENV = undefined;
        assert.notCalled(console.trace);
        webex.logger.trace('test');
        assert.notCalled(console.trace);
  
        process.env.NODE_ENV = 'test';
        webex.logger.trace('test');
        assert.called(console.trace);
      });
  
      nodeOnly(it)('checks the developer feature toggle "log-level" when available', () => {
        assert.notCalled(console.info);
        webex.logger.info('test');
        assert.notCalled(console.info);
  
        webex.internal.device = {
          features: {
            developer: {
              get() {
                return 'info';
              }
            },
            entitlement: {
              get() {
                return false;
              }
            }
          }
        };
        webex.logger.info('test');
        assert.called(console.info);
      });
  
      nodeOnly(it)('doesn\'t break if the feature toggle is set to an incorrect value', () => {
        assert.doesNotThrow(() => {
          assert.notCalled(console.info);
          webex.logger.info('test');
          assert.notCalled(console.info);
  
          webex.internal.device = {
            features: {
              developer: {
                get() {
                  return 'not-a-log-method';
                }
              },
              entitlement: {
                get() {
                  return false;
                }
              }
            }
          };
          webex.logger.info('test');
          assert.notCalled(console.info);
        });
      });
  
      nodeOnly(it)('defaults to "error" for all other users', () => {
        webex.logger.error('test');
        assert.called(console.error);
  
        webex.logger.warn('test');
        assert.notCalled(console.warn);
      });
    });
  
    describe('#filter', () => {
      it('redacts email addresses', () => {
        const message = {
          blarg: 'test@example.com'
        };
  
        assert.deepEqual(webex.logger.filter(message), [{
          blarg: '-- REDACTED --'
        }]);
      });
  
      it('strips auth headers from log output', () => {
        const msg = {
          headers: {
            authorization: 'Bearer'
          },
          options: {
            headers: {
              trackingid: '123',
              authorization: 'Bearer'
            }
          }
        };
  
        assert.doesNotThrow(() => {
          webex.logger.filter({});
          webex.logger.filter({headers: {}});
          webex.logger.filter({headers: {authorization: ''}});
          webex.logger.filter({options: {}});
          webex.logger.filter({options: {headers: {}}});
          webex.logger.filter({options: {headers: {authorization: ''}}});
          webex.logger.filter([{options: {headers: {authorization: ''}}}]);
        });
  
        const [filtered] = webex.logger.filter(msg);
  
        assert.nestedProperty(msg, 'headers.authorization', 'it does not alter the original message');
        assert.nestedProperty(msg, 'options.headers.authorization', 'it does not alter the original message');
  
        assert.notNestedProperty(filtered, 'headers.authorization', 'it removes headers.authorization');
        assert.notNestedProperty(filtered, 'options.headers.authorization', 'it removes options.headers.authorization');
        assert.nestedProperty(msg, 'options.headers.trackingid', 'it does not remove other header values');
        assert.nestedProperty(filtered, 'options.headers.trackingid', 'it does not remove other header values');
      });
    });
  
    [
      'error',
      'warn',
      'log',
      'info',
      'debug',
      'trace'
    ].forEach((level) => {
      describe(`#${level}()`, () => {
        it(`proxies console.${level}`, () => {
          webex.logger.config.level = level;
          assert.notCalled(console[impl(level)]);
          webex.logger[level]('a log statement');
          assert.called(console[impl(level)]);
        });
  
        it('removes authorization data', () => {
          webex.logger.config.level = level;
          webex.logger[level]({
            headers: {
              authorization: 'Bearer',
              trackingid: '123'
            }
          });
          assert.calledWith(console[impl(level)], {
            headers: {
              trackingid: '123'
            }
          });
        });
      });
    });
  
    describe('#walkAndFilter', () => {
      it('redact Authorization', () => {
        webex.config.logger.level = 'trace';
        webex.logger.log({
          Authorization: 'XXXXXXX',
          Key: 'myKey'
        });
  
        // Assert auth was filtered
        assert.calledWith(console.log, {Key: 'myKey'});
  
        webex.logger.log({
          authorization: 'XXXXXXX',
          Key: 'myKey'
        });
  
        assert.calledWith(console.log, {Key: 'myKey'});
      });
  
      it('redact emails', () => {
        webex.config.logger.level = 'trace';
  
        // This behaviour does not seem correct
        webex.logger.log('my email address is test@cisco.com');
        assert.calledWith(console.log, 'my email address is test@cisco.com');
  
        webex.logger.log('test@cisco.com');
        assert.calledWith(console.log, '-- REDACTED --');
      });
  
      it('handle circular references', () => {
        webex.config.logger.level = 'trace';
  
        const object = {
          authorization: 'XXXXXXX',
          string: 'test@cisco.com',
          Key: 'myKey'
        };
  
        // Add a circular reference to the object
        object.selfReference = object;
  
        webex.logger.log(object);
        assert.calledWith(console.log, {Key: 'myKey', selfReference: '[Circular]', string: '-- REDACTED --'});
      });
    });
  });
  