'use strict';

const EventEmitter = require('events').EventEmitter;
const proxyquire = require('proxyquire');

class MockConsul {
  constructor() {
    // Mock the catalog.service.list function.
    this.catalog = {
      service: {
        list: 'catalog.service.list'
      }
    };

    // Mock the health.service function.
    this.health = {
      service: 'health.service'
    };

    // Track the mocked emitters.
    this.emitters = Object.create(null);
  }

  get watching() {
    return Object.keys(this.emitters).sort();
  }

  watch(options) {
    if (options.method === this.catalog.service.list) {
      return this.getOrCreateEmitter('catalog-service');
    }

    if (options.method === this.health.service) {
      let name = options.options.service;

      if (options.options.tag) {
        name += `-${options.options.tag}`;
      }

      return this.getOrCreateEmitter(name);
    }

    throw new Error('Unknown method: ' + options.method);
  }

  emitChange(name, data) {
    this.emitters[name].emit('change', data);
  }

  emitError(name, error) {
    this.emitters[name].emit('error', error);
  }

  getOrCreateEmitter(name) {
    if (!this.emitters[name]) {
      this.emitters[name] = new EventEmitter();
      this.emitters[name].end = () => {
        delete this.emitters[name];
      };
    }
    return this.emitters[name];
  }
}

function generateConsulStub(options) {
  const mock = new MockConsul();
  const Consul = proxyquire('../../lib/source/consul', {
    consul() {
      return mock;
    }
  });
  const consul = new Consul(options);

  consul.mock = mock;
  return consul;
}

module.exports = generateConsulStub;

'use strict';

const EventEmitter = require('events').EventEmitter;
const proxyquire = require('proxyquire');

class MockConsul {
  /**
   * This is a mock that can be injected with proxyrequire when using the
   * Consul package. Currently watchers are mocked for the following APIs:
   *
   * - service.list
   * - health.service
   *
   * Both "change" and "error" events on watchers can be mocked.
   *
   */
  constructor() {
    // Mock the catalog.service.list function.
    this.catalog = {
      service: {
        list: 'catalog.service.list'
      }
    };

    // Mock the health.service function.
    this.health = {
      service: 'health.service'
    };

    // Track the mocked emitters.
    this.emitters = Object.create(null);
  }

  /**
   * Get the names of the active Consul watchers.
   *
   * @return Array  Lexigraphically sorted list of Consul watchers by name.
   */
  get watching() {
    return Object.keys(this.emitters).sort();
  }

  /**
   * Generate a mocked watcher for a Consul API. Watchers are named after the
   * Consul service they're watching. If the service has a tag, the watcher is
   * named "service-tag".
   *
   * @param {Object} options  Options that are passed to the watcher
   * @return {EventEmitter}   The mocked watcher
   */
  watch(options) {
    if (options.method === this.catalog.service.list) {
      return this.getOrCreateEmitter('catalog-service');
    }

    if (options.method === this.health.service) {
      let name = options.options.service;

      if (options.options.tag) {
        name += `-${options.options.tag}`;
      }

      return this.getOrCreateEmitter(name);
    }

    throw new Error('Unknown method: ' + options.method);
  }

  /**
   * Generate a mocked "change" event for a watcher.
   *
   * @param {String} name  Name of the watcher as generated by MockConsul#watch
   * @param {Object} data  Arbitrary data to emit for the event
   */
  emitChange(name, data) {
    this.emitters[name].emit('change', data);
  }

  /**
   * Generate a mocked "error" event for a watcher.
   *
   * @param {String} name  Name of the watcher as generated by MockConsul#watch
   * @param {Object} data  Arbitrary data to emit for the event
   */
  emitError(name, error) {
    this.emitters[name].emit('error', error);
  }

  /**
   * Get or create a new mocked watcher with the provided name
   *
   * @param {String} name    Name of the watcher as generated by MockConsul#watch
   * @return {EventEmitter}  The mocked watcher
   */
  getOrCreateEmitter(name) {
    if (!this.emitters[name]) {
      this.emitters[name] = new EventEmitter();
      this.emitters[name].end = () => {
        delete this.emitters[name];
      };
    }
    return this.emitters[name];
  }
}

/**
 * Create a new mocked instance of the Consul plugin. The MockConsul object can
 * be accessed on the plugin through the "mock" property.
 *
 * @param {Object} options  Options to pass to the Consul plugin's constructor
 * @return {Consul}         An instance of the mocked Consul plugin
 */
function generateConsulStub(options) {
  const mock = new MockConsul();
  const Consul = proxyquire('../../lib/source/consul', {
    consul() {
      return mock;
    }
  });
  const consul = new Consul(options);

  consul.mock = mock;
  return consul;
}

module.exports = generateConsulStub;

Document usage of the Consul stub.