<commit_before>/*jslint node: true, indent: 2, unused: true, maxlen: 80, camelcase: true */
/*
 * stompit.Client
 * Copyright (c) 2013 Graham Daws <graham.daws@gmail.com>
 * MIT licensed
 */

var Socket          = require('./Socket');
var Transaction     = require('./client/Transaction');
var Subscription    = require('./client/Subscription');
var BufferWritable  = require('./util/buffer/BufferWritable');
var util            = require('./util');

var ERROR_MAX_CONTENT_LENGTH = 4096;

// STOMP client connection
function Client(transportSocket, options) {

  options = util.extend({
    commandHandlers: {},
    unknownCommand: onUnknownCommand
  }, options);

  Socket.call(this, transportSocket, options);

  this._receipts = {};
  this._nextReceiptId = 1;

  this._subscriptions = {};
  this._nextSubcriptionId = 1;

  this._nextTransactionId = 1;

  this._disconnecting = false;
  this._hasDisconnectReceipt = false;

  this._resetDisconnect = options.resetDisconnect === true;
}

util.inherits(Client, Socket);

Client.prototype._onInputEnd = function() {

  if (this._hasDisconnectReceipt) {
    this.emit('end');
    this.destroy();
  }
  else {

    var errorMessage = this.hasFinishedOutput() ?
      'connection ended without disconnect receipt' :
      'connection ended unexpectedly';

    this.destroy(this.createProtocolError(errorMessage));
  }
};

Client.prototype._beforeSendResponse = function() {
  // No interception of outgoing frame
};

/*
 * Create frame to send to the server. This method returns a Writable stream
 * object for sending the frame body content.
 */
Client.prototype.sendFrame = function(command, headers, options) {

  if (options) {

    var onReceipt = options.onReceipt;

    if (typeof options.onError === 'function') {

      var originalOnReceipt = onReceipt || function(){};

      var onError = options.onError;

      this.on('error', onError);

      var self = this;
      onReceipt = function() {
        self.removeListener('error', onError);
        originalOnReceipt();
      };
    }

    if (typeof onReceipt === 'function') {

      var id = this._nextReceiptId++;

      this._receipts[id] = onReceipt;

      headers.receipt = id;
    }
  }

  return Socket.prototype.sendFrame.apply(this, arguments);
};

/*
 * Send CONNECT frame to the server.
 */
Client.prototype.connect = function(headers, callback) {

  if (typeof headers === 'string') {
    headers = {'host': headers};
  }

  headers = util.extend(headers, {
    'accept-version': '1.0,1.1,1.2'
  });

  var heartbeat = this.getHeartbeat();

  if (typeof headers['heart-beat'] === "string") {
    var match = headers['heart-beat'].match(/^(\d+) *, *(\d+)$/);
    if (match) {
      heartbeat = [parseInt(match[1], 10), parseInt(match[1], 10)];
      this.setHeartbeat(heartbeat);
    }
  }

  headers['heart-beat'] = heartbeat[0] + "," + heartbeat[1];

  this.setCommandHandlers({
    'CONNECTED': onConnected,
    'ERROR': onError
  });

  if (typeof callback === 'function') {

    var self = this;

    (function() {

      var onConnected = function(client) {
        cleanup();
        callback(null, client);
      };

      var onError = function(error) {
        cleanup();
        callback(error);
      };

      var cleanup = function() {
        self.removeListener('error', onError);
        self.removeListener('connect', onConnected);
      };

      self.on('error', onError);
      self.on('connect', onConnected);
    })();
  }

  var frame = this.sendFrame('CONNECT', headers);

  frame.end();
};

/*
 * Send a message to the server. This method returns a Writable stream object
 * for sending the frame body content.
 */
Client.prototype.send = function(headers, options) {

  if (typeof headers === 'string') {
    headers = {destination: headers};
  }

  return this.sendFrame('SEND', headers, options);
};

Client.prototype.begin = function(headers) {

  if (typeof headers !== 'object') {
    if (typeof headers !== 'undefined') {
      headers = {transaction: headers};
    }
    else {
      headers = {};
    }
  }

  if (!('transaction' in headers)) {
    headers.transaction = this._nextTransactionId++;
  }

  var transaction = new Transaction(headers.transaction, this);

  this.sendFrame('BEGIN', headers).end();

  return transaction;
};

Client.prototype.subscribe = function(headers, messageListener) {

  if (typeof headers === 'string') {
    headers = {destination: headers};
  }

  var id = headers.id !== undefined ? headers.id : this._nextSubcriptionId++;

  while (this._subscriptions[id] !== undefined) {
    id = this._nextSubcriptionId++;
  }

  headers.id = id;

  var ack = headers.ack;

  if (typeof ack === 'undefined') {
    ack = 'auto';
  }

  var validAckModes = [
    'auto', 'client', 'client-individual'
  ];

  if (validAckModes.indexOf(ack) === -1 ) {
    throw new Error('invalid ack mode');
  }

  var subscription = new Subscription(id, ack, messageListener, this);

  this._subscriptions[id] = subscription;

  this.sendFrame('SUBSCRIBE', headers).end();

  return subscription;
};

Client.prototype.getSubscription = function(id) {
  return this._subscriptions[id];
};

/*
 * Perform graceful disconnect from server. This operation does not complete
 * until all messages are acknowledged.
 */
Client.prototype.disconnect = function(callback) {

  var self = this;

  if (typeof callback === 'function') {
    (function() {

      var onEnd = function(client) {
        cleanup();
        callback(null, client);
      };

      var onError = function(error) {
        cleanup();
        callback(error);
      };

      var cleanup = function() {
        self.removeListener('end', onEnd);
        self.removeListener('error', onError);
      };

      self.on('end', onEnd);
      self.on('error', onError);
    })();
  }

  this.sendFrame('DISCONNECT', {}, {
    onReceipt: function() {

      self._hasDisconnectReceipt = true;

      self.getTransportSocket()[self._resetDisconnect ? "destroy" : "end"]();
    }
  }).end(this._finishOutput.bind(this));

  // Keep the transport output open until the receipt is processed just in case
  // the transport is not configured to handle half-open connections.

  this._disconnecting = true;
};

Client.prototype.readEmptyBody = function(frame, callback) {

  var self = this;

  frame.readEmptyBody(function(isEmpty) {

    if (isEmpty) {
      if (typeof callback === 'function') {
        callback.call(self);
      }
    }
    else {
      self.destroy(this.createProtocolError('expected empty body frame'));
    }
  });
};

function onConnected(frame, beforeSendResponse) {

  // If no version header is present then assume the server is running stomp 1.0
  // protocol
  this.setVersion(frame.headers.version || '1.0');

  this.setCommandHandlers({
    'MESSAGE': onMessage,
    'RECEIPT': onReceipt,
    'ERROR': onError
  });

  var self = this;

  this.readEmptyBody(frame, function() {

    if (frame.headers['heart-beat'] !== undefined) {

      var heartbeat = frame.headers['heart-beat']
        .split(',').map(function(x) {
          return parseInt(x, 10);
        });

      if ( heartbeat.length > 1 &&
           !isNaN(heartbeat[0]) &&
           !isNaN(heartbeat[1]) ) {

        this._runHeartbeat(heartbeat[0], heartbeat[1]);
      }
    }

    self.headers = frame.headers;

    self.emit('connect', self);

    beforeSendResponse();
  });
}

function onError(frame) {

  var message = frame.headers.message ? frame.headers.message :
    'server sent ERROR frame';

  var error = this.createApplicationError(message);

  if ( frame.headers['content-type'] === 'text/plain' &&
      frame.headers['content-length'] <= ERROR_MAX_CONTENT_LENGTH) {

    var content = new BufferWritable(new Buffer(ERROR_MAX_CONTENT_LENGTH));

    var self = this;

    frame.on('end', function() {
      error.longMessage = content.getWrittenSlice().toString();
      self.destroy(error);
    });

    frame.pipe(content);
  }
  else {
    this.destroy(error);
  }
}

function onMessage(frame, beforeSendResponse) {

  var subId = frame.headers.subscription;

  var subscription = this._subscriptions[subId];

  if (subscription === undefined) {
    this.destroy(this.createProtocolError('invalid subscription'));
  }

  subscription.processMessageFrame(frame);

  beforeSendResponse();
}

function onReceipt(frame, beforeSendResponse) {

  var id = frame.headers['receipt-id'];

  if (id === undefined || this._receipts[id] === undefined) {
    this.destroy(this.createProtocolError('invalid receipt'));
    return;
  }

  this.readEmptyBody(frame, function() {
    this._receipts[id].call(this);
    delete this._receipts[id];
    beforeSendResponse();
  });
}

function onUnknownCommand(frame) {
  this.destroy(this.createProtocolError(
    'unknown command \'' + frame.command + '\''
  ));
}

module.exports = Client;
<commit_msg>Emit end event after reset disconnect<commit_after>/*jslint node: true, indent: 2, unused: true, maxlen: 80, camelcase: true */
/*
 * stompit.Client
 * Copyright (c) 2013 Graham Daws <graham.daws@gmail.com>
 * MIT licensed
 */

var Socket          = require('./Socket');
var Transaction     = require('./client/Transaction');
var Subscription    = require('./client/Subscription');
var BufferWritable  = require('./util/buffer/BufferWritable');
var util            = require('./util');

var ERROR_MAX_CONTENT_LENGTH = 4096;

// STOMP client connection
function Client(transportSocket, options) {

  options = util.extend({
    commandHandlers: {},
    unknownCommand: onUnknownCommand
  }, options);

  Socket.call(this, transportSocket, options);

  this._receipts = {};
  this._nextReceiptId = 1;

  this._subscriptions = {};
  this._nextSubcriptionId = 1;

  this._nextTransactionId = 1;

  this._disconnecting = false;
  this._hasDisconnectReceipt = false;

  this._resetDisconnect = options.resetDisconnect === true;
}

util.inherits(Client, Socket);

Client.prototype._onInputEnd = function() {

  if (this._hasDisconnectReceipt) {
    this.emit('end');
    this.destroy();
  }
  else {

    var errorMessage = this.hasFinishedOutput() ?
      'connection ended without disconnect receipt' :
      'connection ended unexpectedly';

    this.destroy(this.createProtocolError(errorMessage));
  }
};

Client.prototype._beforeSendResponse = function() {
  // No interception of outgoing frame
};

/*
 * Create frame to send to the server. This method returns a Writable stream
 * object for sending the frame body content.
 */
Client.prototype.sendFrame = function(command, headers, options) {

  if (options) {

    var onReceipt = options.onReceipt;

    if (typeof options.onError === 'function') {

      var originalOnReceipt = onReceipt || function(){};

      var onError = options.onError;

      this.on('error', onError);

      var self = this;
      onReceipt = function() {
        self.removeListener('error', onError);
        originalOnReceipt();
      };
    }

    if (typeof onReceipt === 'function') {

      var id = this._nextReceiptId++;

      this._receipts[id] = onReceipt;

      headers.receipt = id;
    }
  }

  return Socket.prototype.sendFrame.apply(this, arguments);
};

/*
 * Send CONNECT frame to the server.
 */
Client.prototype.connect = function(headers, callback) {

  if (typeof headers === 'string') {
    headers = {'host': headers};
  }

  headers = util.extend(headers, {
    'accept-version': '1.0,1.1,1.2'
  });

  var heartbeat = this.getHeartbeat();

  if (typeof headers['heart-beat'] === "string") {
    var match = headers['heart-beat'].match(/^(\d+) *, *(\d+)$/);
    if (match) {
      heartbeat = [parseInt(match[1], 10), parseInt(match[1], 10)];
      this.setHeartbeat(heartbeat);
    }
  }

  headers['heart-beat'] = heartbeat[0] + "," + heartbeat[1];

  this.setCommandHandlers({
    'CONNECTED': onConnected,
    'ERROR': onError
  });

  if (typeof callback === 'function') {

    var self = this;

    (function() {

      var onConnected = function(client) {
        cleanup();
        callback(null, client);
      };

      var onError = function(error) {
        cleanup();
        callback(error);
      };

      var cleanup = function() {
        self.removeListener('error', onError);
        self.removeListener('connect', onConnected);
      };

      self.on('error', onError);
      self.on('connect', onConnected);
    })();
  }

  var frame = this.sendFrame('CONNECT', headers);

  frame.end();
};

/*
 * Send a message to the server. This method returns a Writable stream object
 * for sending the frame body content.
 */
Client.prototype.send = function(headers, options) {

  if (typeof headers === 'string') {
    headers = {destination: headers};
  }

  return this.sendFrame('SEND', headers, options);
};

Client.prototype.begin = function(headers) {

  if (typeof headers !== 'object') {
    if (typeof headers !== 'undefined') {
      headers = {transaction: headers};
    }
    else {
      headers = {};
    }
  }

  if (!('transaction' in headers)) {
    headers.transaction = this._nextTransactionId++;
  }

  var transaction = new Transaction(headers.transaction, this);

  this.sendFrame('BEGIN', headers).end();

  return transaction;
};

Client.prototype.subscribe = function(headers, messageListener) {

  if (typeof headers === 'string') {
    headers = {destination: headers};
  }

  var id = headers.id !== undefined ? headers.id : this._nextSubcriptionId++;

  while (this._subscriptions[id] !== undefined) {
    id = this._nextSubcriptionId++;
  }

  headers.id = id;

  var ack = headers.ack;

  if (typeof ack === 'undefined') {
    ack = 'auto';
  }

  var validAckModes = [
    'auto', 'client', 'client-individual'
  ];

  if (validAckModes.indexOf(ack) === -1 ) {
    throw new Error('invalid ack mode');
  }

  var subscription = new Subscription(id, ack, messageListener, this);

  this._subscriptions[id] = subscription;

  this.sendFrame('SUBSCRIBE', headers).end();

  return subscription;
};

Client.prototype.getSubscription = function(id) {
  return this._subscriptions[id];
};

/*
 * Perform graceful disconnect from server. This operation does not complete
 * until all messages are acknowledged.
 */
Client.prototype.disconnect = function(callback) {

  var self = this;

  if (typeof callback === 'function') {
    (function() {

      var onEnd = function(client) {
        cleanup();
        callback(null, client);
      };

      var onError = function(error) {
        cleanup();
        callback(error);
      };

      var cleanup = function() {
        self.removeListener('end', onEnd);
        self.removeListener('error', onError);
      };

      self.on('end', onEnd);
      self.on('error', onError);
    })();
  }

  this.sendFrame('DISCONNECT', {}, {
    onReceipt: function() {

      self._hasDisconnectReceipt = true;

      var transport = self.getTransportSocket();

      if (self._resetDisconnect) {
        transport.destroy();
        self.emit('end');
      }
      else{
        transport.end();
      }
    }
  }).end(this._finishOutput.bind(this));

  // Keep the transport output open until the receipt is processed just in case
  // the transport is not configured to handle half-open connections.

  this._disconnecting = true;
};

Client.prototype.readEmptyBody = function(frame, callback) {

  var self = this;

  frame.readEmptyBody(function(isEmpty) {

    if (isEmpty) {
      if (typeof callback === 'function') {
        callback.call(self);
      }
    }
    else {
      self.destroy(this.createProtocolError('expected empty body frame'));
    }
  });
};

function onConnected(frame, beforeSendResponse) {

  // If no version header is present then assume the server is running stomp 1.0
  // protocol
  this.setVersion(frame.headers.version || '1.0');

  this.setCommandHandlers({
    'MESSAGE': onMessage,
    'RECEIPT': onReceipt,
    'ERROR': onError
  });

  var self = this;

  this.readEmptyBody(frame, function() {

    if (frame.headers['heart-beat'] !== undefined) {

      var heartbeat = frame.headers['heart-beat']
        .split(',').map(function(x) {
          return parseInt(x, 10);
        });

      if ( heartbeat.length > 1 &&
           !isNaN(heartbeat[0]) &&
           !isNaN(heartbeat[1]) ) {

        this._runHeartbeat(heartbeat[0], heartbeat[1]);
      }
    }

    self.headers = frame.headers;

    self.emit('connect', self);

    beforeSendResponse();
  });
}

function onError(frame) {

  var message = frame.headers.message ? frame.headers.message :
    'server sent ERROR frame';

  var error = this.createApplicationError(message);

  if ( frame.headers['content-type'] === 'text/plain' &&
      frame.headers['content-length'] <= ERROR_MAX_CONTENT_LENGTH) {

    var content = new BufferWritable(new Buffer(ERROR_MAX_CONTENT_LENGTH));

    var self = this;

    frame.on('end', function() {
      error.longMessage = content.getWrittenSlice().toString();
      self.destroy(error);
    });

    frame.pipe(content);
  }
  else {
    this.destroy(error);
  }
}

function onMessage(frame, beforeSendResponse) {

  var subId = frame.headers.subscription;

  var subscription = this._subscriptions[subId];

  if (subscription === undefined) {
    this.destroy(this.createProtocolError('invalid subscription'));
  }

  subscription.processMessageFrame(frame);

  beforeSendResponse();
}

function onReceipt(frame, beforeSendResponse) {

  var id = frame.headers['receipt-id'];

  if (id === undefined || this._receipts[id] === undefined) {
    this.destroy(this.createProtocolError('invalid receipt'));
    return;
  }

  this.readEmptyBody(frame, function() {
    this._receipts[id].call(this);
    delete this._receipts[id];
    beforeSendResponse();
  });
}

function onUnknownCommand(frame) {
  this.destroy(this.createProtocolError(
    'unknown command \'' + frame.command + '\''
  ));
}

module.exports = Client;
<|endoftext|>


<commit_before>var diff = require('../lib/diff');
var chai = require('chai');
var expect = chai.expect;

describe('#diff()', function() {
    'use strict';

    it('should blow up when given something other than an object or array', function(){
        expect(function() {
            diff.diff({}, '');
        }).to.throw(TypeError);

        expect(function() {
            diff.diff('', {});
        }).to.throw(TypeError);

        expect(function() {
            diff.diff([], {});
        }).to.not.throw(TypeError);
    });

    it('should blow up when an object has a prototype', function(){
        var proto = { foo: 'bar' };

        var WithPrototype = function() {};
        WithPrototype.prototype = proto;

        var obj1 = new WithPrototype(),
            obj2 = { key: 'value' };

        expect(function() {
            diff.diff(obj1, obj2);
        }).to.throw(/has a prototype/);

        expect(function() {
            diff.diff(obj2, obj1);
        }).to.throw(/has a prototype/);
    });

    it('should blow up when an object contains a function', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {f: function() {}};

        expect(function() {
            diff.diff(obj1, obj2);
        }).to.throw(/valid JSON value/);
    });

    it('should support comparing empty objects', function(){
        expect(diff.diff({}, {})).to.be.empty();
    });

    it('should support comparing equal objects', function(){
        var obj1 = {foo: {bar: 'baz'}},
            obj2 = {foo: {bar: 'baz'}};
        expect(diff.diff(obj1, obj2)).to.be.empty();
    });

    it('should support single top-level add', function(){
        var obj1 = {},
            obj2 = {foo: 'bar'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/foo', value: 'bar' });
    });

    it('should support multiple top-level adds', function(){
        var obj1 = {},
            obj2 = {foo: 'bar', baz: 5};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/foo', value: 'bar' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/baz', value: 5 });
    });

    it('should support nested object adds', function(){
        var obj1 = {foo: 'bar', nested: {baz: {}}},
            obj2 = {foo: 'bar', nested: {baz: {key: 'value'}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/nested/baz/key', value: 'value' });
    });

    it('should support single a top-level remove', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/foo' });
    });

    it('should support multiple top-level removes', function(){
        var obj1 = {foo: 'bar', baz: 5},
            obj2 = {};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/foo' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/baz' });
    });

    it('should support nested object removes', function(){
        var obj1 = {nested: {inner: {something: 5}}},
            obj2 = {nested: {inner: {}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/nested/inner/something' });
    });

    it('should support a single top-level replace', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {foo: 'baz'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/foo', value: 'baz' });
    });

    it('should support multiple top-level replaces', function(){
        var obj1 = {foo: 'bar', baz: 5},
            obj2 = {foo: 'baz', baz: 8};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/foo', value: 'baz' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/baz', value: 8 });
    });

    it('should support nested object replaces', function(){
        var obj1 = {nested: {inner: {something: 5}}},
            obj2 = {nested: {inner: {something: 8}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/nested/inner/something', value: 8 });
    });

    it('should support moves at the top-level', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {baz: 'bar'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'move', from: '/foo', path: '/baz' });
    });

    it('should support multiple moves', function(){
        var obj1 = {foo: 'bar', bar: 5},
            obj2 = {baz: 'bar', quux: 5};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'move', from: '/foo', path: '/baz' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'move', from: '/bar', path: '/quux' });
    });

    it('should support moves at the same level of nesting', function(){
        var obj1 = {foo: {bar: 'baz'}},
            obj2 = {foo: {baz: 'baz'}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'move', from: '/foo/bar', path: '/foo/baz' });
    });

    it('should use a separate add and remove for differing levels', function(){
        var obj1 = {foo: {bar: 'baz'}},
            obj2 = {quux: 'baz'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/foo' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/quux', value: 'baz' });
    });

    // TODO: arrays

    // TODO: use quick check
});
<commit_msg>Revert "Add tests for move operations"<commit_after>var diff = require('../lib/diff');
var chai = require('chai');
var expect = chai.expect;

describe('#diff()', function() {
    'use strict';

    it('should blow up when given something other than an object or array', function(){
        expect(function() {
            diff.diff({}, '');
        }).to.throw(TypeError);

        expect(function() {
            diff.diff('', {});
        }).to.throw(TypeError);

        expect(function() {
            diff.diff([], {});
        }).to.not.throw(TypeError);
    });

    it('should blow up when an object has a prototype', function(){
        var proto = { foo: 'bar' };

        var WithPrototype = function() {};
        WithPrototype.prototype = proto;

        var obj1 = new WithPrototype(),
            obj2 = { key: 'value' };

        expect(function() {
            diff.diff(obj1, obj2);
        }).to.throw(/has a prototype/);

        expect(function() {
            diff.diff(obj2, obj1);
        }).to.throw(/has a prototype/);
    });

    it('should blow up when an object contains a function', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {f: function() {}};

        expect(function() {
            diff.diff(obj1, obj2);
        }).to.throw(/valid JSON value/);
    });

    it('should support comparing empty objects', function(){
        expect(diff.diff({}, {})).to.be.empty();
    });

    it('should support comparing equal objects', function(){
        var obj1 = {foo: {bar: 'baz'}},
            obj2 = {foo: {bar: 'baz'}};
        expect(diff.diff(obj1, obj2)).to.be.empty();
    });

    it('should support single top-level add', function(){
        var obj1 = {},
            obj2 = {foo: 'bar'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/foo', value: 'bar' });
    });

    it('should support multiple top-level adds', function(){
        var obj1 = {},
            obj2 = {foo: 'bar', baz: 5};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/foo', value: 'bar' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/baz', value: 5 });
    });

    it('should support nested object adds', function(){
        var obj1 = {foo: 'bar', nested: {baz: {}}},
            obj2 = {foo: 'bar', nested: {baz: {key: 'value'}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'add', path: '/nested/baz/key', value: 'value' });
    });

    it('should support single a top-level remove', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/foo' });
    });

    it('should support multiple top-level removes', function(){
        var obj1 = {foo: 'bar', baz: 5},
            obj2 = {};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/foo' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/baz' });
    });

    it('should support nested object removes', function(){
        var obj1 = {nested: {inner: {something: 5}}},
            obj2 = {nested: {inner: {}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'remove', path: '/nested/inner/something' });
    });

    it('should support a single top-level replace', function(){
        var obj1 = {foo: 'bar'},
            obj2 = {foo: 'baz'};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/foo', value: 'baz' });
    });

    it('should support multiple top-level replaces', function(){
        var obj1 = {foo: 'bar', baz: 5},
            obj2 = {foo: 'baz', baz: 8};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/foo', value: 'baz' });
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/baz', value: 8 });
    });

    it('should support nested object replaces', function(){
        var obj1 = {nested: {inner: {something: 5}}},
            obj2 = {nested: {inner: {something: 8}}};
        expect(diff.diff(obj1, obj2)).to.contain({ op: 'replace', path: '/nested/inner/something', value: 8 });
    });

    // TODO: moves
    // TODO: json patch escaping

    // TODO: arrays

    // TODO: use quick check
});
<|endoftext|>


<commit_before>'use strict';

var async = require('async'),
  events = require('events'),
  util = require('util'),
  Config = require('./Config'),
  DataStore = require('./ds/DataStore'),
  Tenant = require('./resource/Tenant');

function Client(config) {
  var self = this;

  // Call the constructor of the EventEmitter class -- this, allows us to
  // initialize our Client object as an EventEmitter, and allows us to fire off
  // events later on.
  events.EventEmitter.call(this);

  this.config = new Config(config);

  if(!this.config.apiKey){
    this.config.apiKey = this.config.client.apiKey;
  }
  this._dataStore = new DataStore(this.config);

  this._currentTenant = null; //we'll be able to cache this on first call

  this.mergeRemoteConfig(function(err) {
    return err ? self.emit('error',err): self.emit('ready');
  });
}

// By inheriting from `events.EventEmitter`, we're making our Client object a
// true EventEmitter -- allowing us to fire off events.
util.inherits(Client, Object);
util.inherits(Client, events.EventEmitter);

Client.prototype.mergeRemoteConfig = function(callback) {
  var config = this.config;
  var application = config.application;
  var self = this;

  function findApplication(name, cb) {
    self.getApplications({ name: application.name }, function(err, applications) {
      if (err) {
        return cb(err);
      }

      applications.detect(function(app, cb) {
        cb(application.name === app.name);
      }, function(app) {
        if(app){
          self.config.application.href = app.href;
          cb(null,app);
        }else{
          new Error('Application not found by name \''+application.name+'\'');
        }
      });
    });
  }

  function findDefaultDirectoryHref(application, cb) {
    application.getAccountStoreMappings(function(err, mappings) {
      if (err) {
        return cb(err);
      }
      mappings.detect(function(mapping,cb){
        cb(mapping.isDefaultAccountStore);
      },function(defaultMapping){
        if(defaultMapping){
          var href = defaultMapping.accountStore.href;
          if(href.match(/group/)){
            self.getGroup(href,function(err,group){
              cb(err,group &&group.directory.href);
            });
          }else{
            cb(null,href);
          }
        }else{
          cb(null,null);
        }
      });
    });
  }

  function applyConfig(directoryHref, cb) {
    if(!directoryHref){
      callback(null);
    }else{
      self.getDirectory(
        directoryHref,
        {expand:'passwordPolicy,accountCreationPolicy'},
        function(err,directory){
          if(err){
            cb(err);
          }else{
            self.config.web.verifyEmail.enabled =
              directory.accountCreationPolicy.verificationEmailStatus === 'ENABLED';
            self.config.web.changePassword.enabled = self.config.web.forgotPassword.enabled =
              directory.passwordPolicy.resetEmailStatus === 'ENABLED';
            cb(null);
          }
        }
      );
    }
  }

  if (application && application.href) {
    async.waterfall([
      self.getApplication.bind(self, application.href),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else if (application && application.name) {
    async.waterfall([
      findApplication(application.name),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else {
    process.nextTick(callback);
  }

};

Client.prototype.getCurrentTenant = function getCurrentTenant(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  //check to see if cached (tenant never changes for a given API Key):
  if (self._currentTenant) {
    return callback(null, self._currentTenant);
  }

  //not cached - need to look it up (and cache it):
  return self._dataStore.getResource('/tenants/current', options, Tenant, function onCurrentTenant(err, tenant) {
    if (err) {
      return callback(err, null);
    }

    self._currentTenant = tenant;
    return callback(null, tenant);
  });
};

/**
 * Queries the server for a resource by `href`, providing the result to `callback`.
 *
 * @param {String} href the URI of the resource to acquire - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `href`
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`) The Constructor function to invoke for any Instance Resource returned by the server.  If the resource returned by the server is a single resource, this function is used to construct the reasource.  If the resource returned by the server is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource.  Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method getResource
 * @version 0.1
 */
Client.prototype.getResource = function getResource(/* href, [query,] [InstanceConstructor,] callback */) {
  return this._dataStore.getResource.apply(this._dataStore, arguments);
};

/**
 * Creates a new resource on the server as a child of the specified `parentHref` location.  `parentHref` must be a collection resource endpoint.
 *
 * @param {String} parentHref the URI of the parent collection under which the new resource will be created as a child - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `parentHref`
 * @param {Object} data the resource (name/value pairs) to send to the server.
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`)  The Constructor function to invoke for any instance resource returned by the server.  If the request result is a single resource, this function is used to construct the resource.  If the request result is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource. Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method createResource
 * @version 0.1
 */
Client.prototype.createResource = function createResource(/* parentHref, [query,] data, [InstanceConstructor,] callback */) {
  this._dataStore.createResource.apply(this._dataStore, arguments);
};

//convenience methods:

Client.prototype.getApplications = function getClientApplications(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForApplications(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getApplications(options, callback);
  });
};

Client.prototype.createApplication = function createClientApplication(/* app, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var app = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateApplication(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createApplication(app, options, callback);
  });
};

Client.prototype.getDirectories = function getClientDirectories(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForDirectories(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getDirectories(options, callback);
  });
};

Client.prototype.createDirectory = function createClientDirectory(/* dir, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var dir = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateDirectory(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createDirectory(dir, options, callback);
  });
};

// Individual resource type convenience methods:
Client.prototype.getAccount = function getClientAccount(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Account'), callback);
};
Client.prototype.getApplication = function getClientApplication(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Application'), callback);
};
Client.prototype.getDirectory = function getClientDirectory(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Directory'), callback);
};
Client.prototype.getGroup = function getClientGroup(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Group'), callback);
};
Client.prototype.getGroupMembership = function getClientGroupMembership(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/GroupMembership'), callback);
};

module.exports = Client;
<commit_msg>Adding comments about backwards compatibility into the client.<commit_after>'use strict';

var async = require('async'),
  events = require('events'),
  util = require('util'),
  Config = require('./Config'),
  DataStore = require('./ds/DataStore'),
  Tenant = require('./resource/Tenant');

function Client(config) {
  var self = this;

  // Call the constructor of the EventEmitter class -- this, allows us to
  // initialize our Client object as an EventEmitter, and allows us to fire off
  // events later on.
  events.EventEmitter.call(this);

  // Bind the user-supplied config property to our class so it can be accessed
  // like an object.
  this.config = new Config(config);

  // For backwards compatibility reasons, if no API key is specified we'll try
  // to grab the API credentials out of our new format and shove it into the old
  // format.  This can go away once we cut a release and decide to no longer
  // support the old configuration formatting.
  if (!this.config.apiKey) {
    this.config.apiKey = this.config.client.apiKey;
  }

  this._dataStore = new DataStore(this.config);

  this._currentTenant = null; //we'll be able to cache this on first call

  this.mergeRemoteConfig(function(err) {
    return err ? self.emit('error',err): self.emit('ready');
  });
}

// By inheriting from `events.EventEmitter`, we're making our Client object a
// true EventEmitter -- allowing us to fire off events.
util.inherits(Client, Object);
util.inherits(Client, events.EventEmitter);

Client.prototype.mergeRemoteConfig = function(callback) {
  var config = this.config;
  var application = config.application;
  var self = this;

  function findApplication(name, cb) {
    self.getApplications({ name: application.name }, function(err, applications) {
      if (err) {
        return cb(err);
      }

      applications.detect(function(app, cb) {
        cb(application.name === app.name);
      }, function(app) {
        if(app){
          self.config.application.href = app.href;
          cb(null,app);
        }else{
          new Error('Application not found by name \''+application.name+'\'');
        }
      });
    });
  }

  function findDefaultDirectoryHref(application, cb) {
    application.getAccountStoreMappings(function(err, mappings) {
      if (err) {
        return cb(err);
      }
      mappings.detect(function(mapping,cb){
        cb(mapping.isDefaultAccountStore);
      },function(defaultMapping){
        if(defaultMapping){
          var href = defaultMapping.accountStore.href;
          if(href.match(/group/)){
            self.getGroup(href,function(err,group){
              cb(err,group &&group.directory.href);
            });
          }else{
            cb(null,href);
          }
        }else{
          cb(null,null);
        }
      });
    });
  }

  function applyConfig(directoryHref, cb) {
    if(!directoryHref){
      callback(null);
    }else{
      self.getDirectory(
        directoryHref,
        {expand:'passwordPolicy,accountCreationPolicy'},
        function(err,directory){
          if(err){
            cb(err);
          }else{
            self.config.web.verifyEmail.enabled =
              directory.accountCreationPolicy.verificationEmailStatus === 'ENABLED';
            self.config.web.changePassword.enabled = self.config.web.forgotPassword.enabled =
              directory.passwordPolicy.resetEmailStatus === 'ENABLED';
            cb(null);
          }
        }
      );
    }
  }

  if (application && application.href) {
    async.waterfall([
      self.getApplication.bind(self, application.href),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else if (application && application.name) {
    async.waterfall([
      findApplication(application.name),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else {
    process.nextTick(callback);
  }

};

Client.prototype.getCurrentTenant = function getCurrentTenant(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  //check to see if cached (tenant never changes for a given API Key):
  if (self._currentTenant) {
    return callback(null, self._currentTenant);
  }

  //not cached - need to look it up (and cache it):
  return self._dataStore.getResource('/tenants/current', options, Tenant, function onCurrentTenant(err, tenant) {
    if (err) {
      return callback(err, null);
    }

    self._currentTenant = tenant;
    return callback(null, tenant);
  });
};

/**
 * Queries the server for a resource by `href`, providing the result to `callback`.
 *
 * @param {String} href the URI of the resource to acquire - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `href`
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`) The Constructor function to invoke for any Instance Resource returned by the server.  If the resource returned by the server is a single resource, this function is used to construct the reasource.  If the resource returned by the server is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource.  Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method getResource
 * @version 0.1
 */
Client.prototype.getResource = function getResource(/* href, [query,] [InstanceConstructor,] callback */) {
  return this._dataStore.getResource.apply(this._dataStore, arguments);
};

/**
 * Creates a new resource on the server as a child of the specified `parentHref` location.  `parentHref` must be a collection resource endpoint.
 *
 * @param {String} parentHref the URI of the parent collection under which the new resource will be created as a child - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `parentHref`
 * @param {Object} data the resource (name/value pairs) to send to the server.
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`)  The Constructor function to invoke for any instance resource returned by the server.  If the request result is a single resource, this function is used to construct the resource.  If the request result is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource. Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method createResource
 * @version 0.1
 */
Client.prototype.createResource = function createResource(/* parentHref, [query,] data, [InstanceConstructor,] callback */) {
  this._dataStore.createResource.apply(this._dataStore, arguments);
};

//convenience methods:

Client.prototype.getApplications = function getClientApplications(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForApplications(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getApplications(options, callback);
  });
};

Client.prototype.createApplication = function createClientApplication(/* app, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var app = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateApplication(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createApplication(app, options, callback);
  });
};

Client.prototype.getDirectories = function getClientDirectories(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForDirectories(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getDirectories(options, callback);
  });
};

Client.prototype.createDirectory = function createClientDirectory(/* dir, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var dir = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateDirectory(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createDirectory(dir, options, callback);
  });
};

// Individual resource type convenience methods:
Client.prototype.getAccount = function getClientAccount(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Account'), callback);
};
Client.prototype.getApplication = function getClientApplication(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Application'), callback);
};
Client.prototype.getDirectory = function getClientDirectory(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Directory'), callback);
};
Client.prototype.getGroup = function getClientGroup(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Group'), callback);
};
Client.prototype.getGroupMembership = function getClientGroupMembership(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/GroupMembership'), callback);
};

module.exports = Client;
<|endoftext|>


<commit_before>(function () {
  'use strict';
  /*jshint -W003*/
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    // NodeJS
    getPayload(require('chai'), require('../index'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define([
      'chai',
      'chai-json-schema',
    ], function (chai, plugin) {
      return getPayload(chai, plugin);
    });
  } else {
    // Other environment (usually <script> tag): plug in to global chai instance directly.
    return getPayload(window.chai, null);
  }

  function getPayload(chai, plugin) {
    if (plugin) {
      chai.use(plugin);
    }
    chai.should();
    chai.Assertion.includeStack = true;

    var assert = chai.assert;

    describe('chai-json-schema fail demo', function () {
      chai.use(function (chai, utils) {
        var inspect = utils.objDisplay;
        chai.Assertion.addMethod('fail', function (message) {
          var obj = this._obj;

          new chai.Assertion(obj).is.a('function');

          try {
            obj();
          } catch (err) {
            this.assert(
              err instanceof chai.AssertionError
              , message + ' expected #{this} to fail, but it threw ' + inspect(err));
            /*this.assert(
             err.message === message
             , 'expected #{this} to fail with ' + inspect(message) + ', but got ' + inspect(err.message));*/
            return;
          }

          this.assert(false, message + ' expected #{this} to fail');
        });
      });
      var tests = [{
        name: 'properties',
        schema: {
          properties: {
            intKey: {
              type: 'integer'
            },
            stringKey: {
              type: 'string'
            }
          }
        },
        valid: [{
          data: {
            intKey: 1,
            stringKey: 'one'
          }
        }],
        invalid: [{
          data: {
            intKey: 3,
            stringKey: false
          }
        }]
      }, {
        name: 'combinations',
        schema: {
          id: 'any_v1',
          anyOf: [
            { type: 'integer' },
            { type: 'string' }
          ]
        },
        valid: [
          { data: 1 },
          { data: 'yo' }
        ],
        invalid: [
          { data: [1, 2, 3] },
          { data: { aa: 1 } }
        ]
      }, {
        name: 'fruit',
        schema: {
          id: 'fruit_v1',
          description: 'fresh fruit schema v1',
          type: 'object',
          properties: {
            required: ['skin', 'colors', 'taste'],
            colors: {
              type: 'array',
              minItems: 1,
              uniqueItems: true,
              items: {
                type: 'string'
              }
            },
            skin: {
              type: 'string'
            },
            taste: {
              type: 'number',
              minimum: 5
            }
          }
        },
        valid: [{
          data: {
            skin: 'thin',
            colors: ['red', 'green', 'yellow'],
            taste: 10
          }
        }],
        invalid: [{
          data: {
            colors: ['brown'],
            taste: 0,
            worms: 2
          }
        }]
      }];

      it('has tests', function () {
        assert.isArray(tests, 'tests');
        assert.operator(tests.length, '>', 0, 'tests.length');
      });

      Object.keys(tests).forEach(function (key) {
        var testCase = tests[key];
        describe('test ' + testCase.name, function () {
          it('has settings', function () {
            assert.isObject(testCase.schema, 'schema');

            assert.isArray(testCase.valid, 'valid');
            assert.operator(testCase.valid.length, '>', 0, 'valid.length');

            assert.isArray(testCase.invalid, 'invalid');
            assert.operator(testCase.invalid.length, '>', 0, 'invalid.length');
          });
        });
      });

      // enable this to see the error reporting
      describe('bulk', function () {
        Object.keys(tests).forEach(function (key) {
          var testCase = tests[key];
          describe(testCase.name + ' schema', function () {
            it('assert.jsonSchema()', function () {
              testCase.invalid.forEach(function (obj, i) {
                assert.notJsonSchema(obj.data, testCase.schema, '#' + i);
              });
            });
          });
        });
      });
    });
  }
}());
<commit_msg>Remove comment<commit_after>(function () {
  'use strict';
  /*jshint -W003*/
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    // NodeJS
    getPayload(require('chai'), require('../index'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define([
      'chai',
      'chai-json-schema',
    ], function (chai, plugin) {
      return getPayload(chai, plugin);
    });
  } else {
    // Other environment (usually <script> tag): plug in to global chai instance directly.
    return getPayload(window.chai, null);
  }

  function getPayload(chai, plugin) {
    if (plugin) {
      chai.use(plugin);
    }
    chai.should();
    chai.Assertion.includeStack = true;

    var assert = chai.assert;

    describe('chai-json-schema fail demo', function () {
      chai.use(function (chai, utils) {
        var inspect = utils.objDisplay;
        chai.Assertion.addMethod('fail', function (message) {
          var obj = this._obj;

          new chai.Assertion(obj).is.a('function');

          try {
            obj();
          } catch (err) {
            this.assert(
              err instanceof chai.AssertionError
              , message + ' expected #{this} to fail, but it threw ' + inspect(err));
            return;
          }

          this.assert(false, message + ' expected #{this} to fail');
        });
      });
      var tests = [{
        name: 'properties',
        schema: {
          properties: {
            intKey: {
              type: 'integer'
            },
            stringKey: {
              type: 'string'
            }
          }
        },
        valid: [{
          data: {
            intKey: 1,
            stringKey: 'one'
          }
        }],
        invalid: [{
          data: {
            intKey: 3,
            stringKey: false
          }
        }]
      }, {
        name: 'combinations',
        schema: {
          id: 'any_v1',
          anyOf: [
            { type: 'integer' },
            { type: 'string' }
          ]
        },
        valid: [
          { data: 1 },
          { data: 'yo' }
        ],
        invalid: [
          { data: [1, 2, 3] },
          { data: { aa: 1 } }
        ]
      }, {
        name: 'fruit',
        schema: {
          id: 'fruit_v1',
          description: 'fresh fruit schema v1',
          type: 'object',
          properties: {
            required: ['skin', 'colors', 'taste'],
            colors: {
              type: 'array',
              minItems: 1,
              uniqueItems: true,
              items: {
                type: 'string'
              }
            },
            skin: {
              type: 'string'
            },
            taste: {
              type: 'number',
              minimum: 5
            }
          }
        },
        valid: [{
          data: {
            skin: 'thin',
            colors: ['red', 'green', 'yellow'],
            taste: 10
          }
        }],
        invalid: [{
          data: {
            colors: ['brown'],
            taste: 0,
            worms: 2
          }
        }]
      }];

      it('has tests', function () {
        assert.isArray(tests, 'tests');
        assert.operator(tests.length, '>', 0, 'tests.length');
      });

      Object.keys(tests).forEach(function (key) {
        var testCase = tests[key];
        describe('test ' + testCase.name, function () {
          it('has settings', function () {
            assert.isObject(testCase.schema, 'schema');

            assert.isArray(testCase.valid, 'valid');
            assert.operator(testCase.valid.length, '>', 0, 'valid.length');

            assert.isArray(testCase.invalid, 'invalid');
            assert.operator(testCase.invalid.length, '>', 0, 'invalid.length');
          });
        });
      });

      // enable this to see the error reporting
      describe('bulk', function () {
        Object.keys(tests).forEach(function (key) {
          var testCase = tests[key];
          describe(testCase.name + ' schema', function () {
            it('assert.jsonSchema()', function () {
              testCase.invalid.forEach(function (obj, i) {
                assert.notJsonSchema(obj.data, testCase.schema, '#' + i);
              });
            });
          });
        });
      });
    });
  }
}());
<|endoftext|>


<commit_before>/*jslint node:true, browser:true, nomen:true */
/*globals before, describe, after, it */

'use strict';

var assert = require('assert');
var util = require('util');
var fs = require('fs');

var File = require('../lib/file').File;
var MetaEvent = require('../lib/file/event').MetaEvent;
var SysexEvent = require('../lib/file/event').SysexEvent;
var ChannelEvent = require('../lib/file/event').ChannelEvent;

var filesPath = __dirname + '/files/';
var pathFormat = filesPath + '%d_%d_time.mid';
var times = [
    [3, 4, true],  [4, 4, true],  [6, 8, true],  [9, 8, true],  [12, 8, true],
    [3, 4, false], [4, 4, false], [6, 8, false], [9, 8, false], [12, 8, false]
];

describe('File as a writer', function () {
    var file;

    it('should create a new file', function () {
        file = new File();

        assert.strictEqual(file.header.fileType, 1);
        assert.strictEqual(file.header.trackCount, 0);
        assert.strictEqual(file.header.ticksPerBeat, 120);

        assert.ok(Array.isArray(file.tracks));
        assert.strictEqual(file.tracks.length, file.header.trackCount);
    });

    describe('tracks edition', function () {
        it('should add a track', function () {
            var track = file.addTrack();
        });
    });
});

describe('File as a reader', function () {
    times.forEach(function (time) {
        var path = util.format(pathFormat, time[0], time[1]), file,
            streamMessage = (time[2]) ? 'read with streams' : 'read with buffers',
            message = util.format('file %s (%s)', path, streamMessage);

        describe(message, function () {
            before(function (done) {
                if (time[2]) {
                    file = new File();
                    file.on('parsed', done);

                    fs.createReadStream(path).pipe(file);
                } else {
                    fs.readFile(path, function (err, data) {
                        if (err) {
                            throw err;
                        }

                        file = new File(data);
                        file.parse(done);
                    });
                }
            });

            it('should parse the header chunk', function () {
                assert.strictEqual(typeof file.header.fileType, 'number');
                assert.ok(file.header.fileType >= 0);
                assert.ok(file.header.fileType <= 2);

                assert.strictEqual(typeof file.header.trackCount, 'number');
                assert.ok(file.header.trackCount > 0, 'number');

                assert.strictEqual(typeof file.header.ticksPerBeat, 'number');
                assert.ok(file.header.ticksPerBeat > 0);
            });

            it('should parse the tracks chunks as a list of tracks', function () {
                assert.ok(Array.isArray(file.tracks));
                assert.strictEqual(file.tracks.length, file.header.trackCount);
            });

            it('should parse each track chunk', function () {
                var channelTypes, metaTypes;

                channelTypes = ['noteOff', 'noteOn', 'noteAftertouch',
                                'controller', 'programChange',
                                'channelAftertouch', 'pitchBend'];
                metaTypes = ['sequenceNumber', 'text', 'copyrightNotice',
                            'sequenceName', 'instumentName', 'lyrics',
                            'marker', 'cuePoint', 'channelPrefix',
                            'endOfTrack', 'setTempo', 'timeSignature',
                            'keySignature', 'sequencerSpecific'];

                file.tracks.forEach(function (track) {
                    assert.ok(Array.isArray(track.events));

                    track.events.forEach(function (event, index) {
                        assert.strictEqual(typeof event.delay, 'number');

                        if (index === track.events.length - 1) {
                            assert.ok(event instanceof MetaEvent);
                            assert.strictEqual(event.type, 'endOfTrack');
                            return;
                        }

                        if (event instanceof MetaEvent) {
                            assert.notStrictEqual(
                                metaTypes.indexOf(event.type),
                                -1
                            );

                            switch (event.type) {
                            case 'timeSignature':
                                assert.strictEqual(event.numerator, time[0]);
                                assert.strictEqual(event.denominator, time[1]);
                                assert.strictEqual(typeof event.metronome, 'number');
                                assert.strictEqual(event.clockSignalsPerBeat, 24);
                                break;

                            case 'keySignature':
                                assert.ok(event.major);
                                assert.strictEqual(event.note, 0);
                                break;

                            case 'setTempo':
                                assert.strictEqual(event.tempo, 600000);
                                break;
                            }
                        } else if (event instanceof SysexEvent) {

                        } else if (event instanceof ChannelEvent) {
                            assert.strictEqual(typeof event.channel, 'number');
                            assert.ok(event.channel >= 0);
                            assert.ok(event.channel <= 15);

                            assert.notStrictEqual(
                                channelTypes.indexOf(event.type),
                                -1
                            );
                        } else {
                            throw new Error('Expected event of type Meta, Sysex or Channel');
                        }
                    });
                });
            });
        });
    });

    describe('invalid file "' + filesPath + 'invalid_file.mid"', function () {
        var path = filesPath + 'invalid_file.mid', file;

        it('should throw on parsing the file', function (done) {
            fs.readFile(path, function (err, data) {
                if (err) {
                    throw err;
                }

                file = new File(data);

                file.on('error', function (error) {
                    assert.ok(/invalid midi/i.test(error.message));
                    done();
                });

                file.parse();
            });
        });
    });
});
<commit_msg>:lipstick: Comply to ESLint<commit_after>/*eslint-env mocha */

'use strict';

var assert = require('assert');
var path = require('path');
var util = require('util');
var fs = require('fs');

var File = require('../lib/file').File;
var MetaEvent = require('../lib/file/event').MetaEvent;
var SysexEvent = require('../lib/file/event').SysexEvent;
var ChannelEvent = require('../lib/file/event').ChannelEvent;

var filesPath = path.join(__dirname, 'files');
var pathFormat = path.join(filesPath, '%d_%d_time.mid');
var times = [
    [3, 4, true], [4, 4, true], [6, 8, true], [9, 8, true], [12, 8, true],
    [3, 4, false], [4, 4, false], [6, 8, false], [9, 8, false], [12, 8, false]
];

describe('File as a writer', function () {
    var file;

    it('should create a new file', function () {
        file = new File();

        assert.strictEqual(file.header.fileType, 1);
        assert.strictEqual(file.header.trackCount, 0);
        assert.strictEqual(file.header.ticksPerBeat, 120);

        assert.ok(Array.isArray(file.tracks));
        assert.strictEqual(file.tracks.length, file.header.trackCount);
    });

    describe('tracks edition', function () {
        it('should add a track', function () {
            var track = file.addTrack();
        });
    });
});

describe('File as a reader', function () {
    times.forEach(function (time) {
        var filePath = util.format(pathFormat, time[0], time[1]), file,
            streamMessage = (time[2]) ? 'read with streams' : 'read with buffers',
            message = util.format('file %s (%s)', path, streamMessage);

        describe(message, function () {
            before(function (done) {
                if (time[2]) {
                    file = new File();
                    file.on('parsed', done);

                    fs.createReadStream(filePath).pipe(file);
                } else {
                    fs.readFile(filePath, function (err, data) {
                        if (err) {
                            throw err;
                        }

                        file = new File(data);
                        file.parse(done);
                    });
                }
            });

            it('should parse the header chunk', function () {
                assert.strictEqual(typeof file.header.fileType, 'number');
                assert.ok(file.header.fileType >= 0);
                assert.ok(file.header.fileType <= 2);

                assert.strictEqual(typeof file.header.trackCount, 'number');
                assert.ok(file.header.trackCount > 0, 'number');

                assert.strictEqual(typeof file.header.ticksPerBeat, 'number');
                assert.ok(file.header.ticksPerBeat > 0);
            });

            it('should parse the tracks chunks as a list of tracks', function () {
                assert.ok(Array.isArray(file.tracks));
                assert.strictEqual(file.tracks.length, file.header.trackCount);
            });

            it('should parse each track chunk', function () {
                var channelTypes, metaTypes;

                channelTypes = ['noteOff', 'noteOn', 'noteAftertouch',
                                'controller', 'programChange',
                                'channelAftertouch', 'pitchBend'];
                metaTypes = ['sequenceNumber', 'text', 'copyrightNotice',
                            'sequenceName', 'instumentName', 'lyrics',
                            'marker', 'cuePoint', 'channelPrefix',
                            'endOfTrack', 'setTempo', 'timeSignature',
                            'keySignature', 'sequencerSpecific'];

                file.tracks.forEach(function (track) {
                    assert.ok(Array.isArray(track.events));

                    track.events.forEach(function (event, index) {
                        assert.strictEqual(typeof event.delay, 'number');

                        if (index === track.events.length - 1) {
                            assert.ok(event instanceof MetaEvent);
                            assert.strictEqual(event.type, 'endOfTrack');
                            return;
                        }

                        if (event instanceof MetaEvent) {
                            assert.notStrictEqual(
                                metaTypes.indexOf(event.type),
                                -1
                            );

                            switch (event.type) {
                            case 'timeSignature':
                                assert.strictEqual(event.numerator, time[0]);
                                assert.strictEqual(event.denominator, time[1]);
                                assert.strictEqual(typeof event.metronome, 'number');
                                assert.strictEqual(event.clockSignalsPerBeat, 24);
                                break;

                            case 'keySignature':
                                assert.ok(event.major);
                                assert.strictEqual(event.note, 0);
                                break;

                            case 'setTempo':
                                assert.strictEqual(event.tempo, 600000);
                                break;
                            }
                        } else if (event instanceof SysexEvent) {

                        } else if (event instanceof ChannelEvent) {
                            assert.strictEqual(typeof event.channel, 'number');
                            assert.ok(event.channel >= 0);
                            assert.ok(event.channel <= 15);

                            assert.notStrictEqual(
                                channelTypes.indexOf(event.type),
                                -1
                            );
                        } else {
                            throw new Error('Expected event of type Meta, Sysex or Channel');
                        }
                    });
                });
            });
        });
    });

    describe('invalid file "' + filesPath + 'invalid_file.mid"', function () {
        var filePath = path.join(filesPath, 'invalid_file.mid'), file;

        it('should throw on parsing the file', function (done) {
            fs.readFile(filePath, function (err, data) {
                if (err) {
                    throw err;
                }

                file = new File(data);

                file.on('error', function (error) {
                    assert.ok(/invalid midi/i.test(error.message));
                    done();
                });

                file.parse();
            });
        });
    });
});
<|endoftext|>


<commit_before>"use strict";

const fs        = require("fs");
const merge     = require("merge");
const minimatch = require("minimatch");

/**
 * Configurações
 *
 * @constructor
 * @param {string[]} pattern Padrão de Captura
 * @param {fs}       fs      Manipulador de Sistema de Arquivos
 */
function Config (pattern, fs)
{
    this.setPattern(pattern);
    this.setFs(fs);
};

/**
 * Configura o Padrão de Captura
 *
 * @param  {string[]} pattern Valores para Configuração
 * @return undefined
 */
Config.prototype.setPattern = function (pattern)
{
    this._pattern = pattern;
};

/**
 * Apresenta o Padrão de Captura
 *
 * @return {string[]} Valores Configurados
 */
Config.prototype.getPattern = function ()
{
    return this._pattern;
};

/**
 * Configura o Manipulador de Sistema de Arquivos
 *
 * @param  {fs}      fs Elemento para Configuração
 * @return undefined
 */
Config.prototype.setFs = function (fs)
{
    this._fs = fs;
};

/**
 * Apresenta o Manipulador de Sistema de Arquivos
 *
 * @return {fs} Elemento Configurado
 */
Config.prototype.getFs = function ()
{
    return this._fs || fs;
};

/**
 * Busca Nomes de Arquivos
 *
 * @return {string[]} Lista de Arquivos Encontrados
 */
Config.prototype._fetchFilenames = function ()
{
    // Inicialização
    var fs        = this.getFs();
    var pattern   = this.getPattern();
    var filenames = [];
    // Inclui Prefixo
    pattern = pattern.map(function (pattern) {
        // Prefixado?
        if (pattern.indexOf("./") === 0) {
            // Remove Prefixo
            pattern = pattern.substring(2);
        }
        // Apresentação
        return pattern;
    });
    // Função: Captura Recursiva
    var fn = function (directory) {
        // Leitura de Diretório
        fs.readdirSync(directory).forEach(function (filename) {
            // Caminho Completo
            filename = directory + "/" + filename;
            // Diretório?
            if (fs.statSync(filename).isDirectory()) {
                // Recursão
                fn(filename);
                return;
            }
            // Captura
            filenames.push(filename.substr(2));
        });
    };
    // Execução
    fn(".");
    // Apresentação
    return filenames.filter(function (filename) {
        return pattern.filter(function (pattern) {
            return minimatch(filename, pattern);
        }).length > 0;
    });
};

/**
 * Mescla Elementos de Configuração
 *
 * Recebe como parâmetro um conjunto de objetos que possuem dois parâmetros: `filename` e `content`. Apresenta como
 * resultado os parâmetros `content` mesclados em ordem alfabética de `filename`.
 *
 * @param  {object[]} elements Informações para Mesclagem
 * @return object     Elemento Resultante
 */
Config.prototype._merge = function (elements)
{
    // Ordenação
    elements.sort(function (a, b) {
        return a.filename.localeCompare(b.filename);
    });

    // Apresentação
    return elements.reduce(function (elements, item) {
        return merge(elements, item.content);
    }, {});
};

/**
 * Lê Arquivo de Forma Síncrona
 *
 * Executa a leitura do arquivo informado como parâmetro, apresentando um objeto que representa o seu conteúdo. Caso
 * algum problema seja encontrado, atira um objeto de erro. O objeto apresentado possui dois atributos: `filename` com o
 * valor idêntico ao primeiro parâmetro deste método, e `content` com um objeto que representa o conteúdo JSON do
 * arquivo.
 *
 * @param  {string} filename Nome do Arquivo
 * @throws {Error}  Erro Encontrado durante Processamento
 * @return {object} Objeto que Representa o Conteúdo do Arquivo
 */
Config.prototype._readAndBuildSync = function (filename)
{
    return {"filename": filename, "content": JSON.parse(this.getFs().readFileSync(filename))};
};

/**
 * Busca Configurações
 *
 * @return {Promise} Execução Assíncrona
 */
Config.prototype.fetch = function ()
{
    // Inicialização
    var self     = this;
    var promises = [];

    // Consulta
    this._fetchFilenames().forEach(function (filename) {
        // Execução Assíncrona
        promises.push(new Promise(function (resolve, reject) {
            // Tratamento
            try {
                // Sucesso!
                var element = self._readAndBuildSync(filename);
                // Apresentação
                resolve(element);
            } catch (error) {
                // Impossível Continuar
                reject(error);
            }
        }));
    });

    // Execução
    return Promise.all(promises).then(this._merge);
};

/**
 * Busca Configurações de Forma Síncrona
 *
 * @return {object} Conteúdo Solicitado
 */
Config.prototype.fetchSync = function ()
{
    // Inicialização
    var self = this;

    // Execução
    return this._merge(this._fetchFilenames().map(function (filename) {
        return self._readAndBuildSync(filename);
    }));
};

// Configuração
module.exports = Config;
<commit_msg>Padroniza código-fonte de biblioteca<commit_after>'use strict';

const fs = require('fs');
const merge = require('merge');
const minimatch = require('minimatch');

/**
 * Configurações
 *
 * @constructor
 * @param {string[]} pattern Padrão de Captura
 * @param {fs}       aFs     Manipulador de Sistema de Arquivos
 */
function Config(pattern, aFs) {
  this.setPattern(pattern);
  this.setFs(aFs);
}

/**
 * Configura o Padrão de Captura
 *
 * @param  {string[]} pattern Valores para Configuração
 * @return undefined
 */
Config.prototype.setPattern = function setPattern(pattern) {
  this.pattern = pattern;
};

/**
 * Apresenta o Padrão de Captura
 *
 * @return {string[]} Valores Configurados
 */
Config.prototype.getPattern = function getPattern() {
  return this.pattern;
};

/**
 * Configura o Manipulador de Sistema de Arquivos
 *
 * @param  {fs}      aFs Elemento para Configuração
 * @return undefined
 */
Config.prototype.setFs = function setFs(aFs) {
  this.fs = aFs;
};

/**
 * Apresenta o Manipulador de Sistema de Arquivos
 *
 * @return {fs} Elemento Configurado
 */
Config.prototype.getFs = function getFs() {
  return this.fs || fs;
};

/**
 * Busca Nomes de Arquivos
 *
 * @return {string[]} Lista de Arquivos Encontrados
 */
Config.prototype.fetchFilenames = function fetchFilenames() {
  // Inicialização
  const aFs = this.getFs();
  const filenames = [];
  // Inclui Prefixo
  const pattern = this.getPattern().map((aPattern) => {
    // Inicialização
    let result = aPattern;
    // Prefixado?
    if (result.indexOf('./') === 0) {
      // Remove Prefixo
      result = result.substring(2);
    }
    // Apresentação
    return result;
  });
  // Função: Captura Recursiva
  const fn = (directory) => {
    // Leitura de Diretório
    aFs.readdirSync(directory).forEach((filename) => {
      // Caminho Completo
      const filepath = `${directory}/${filename}`;
      // Diretório?
      if (aFs.statSync(filepath).isDirectory()) {
        // Recursão
        fn(filepath);
        return;
      }
      // Captura
      filenames.push(filepath.substr(2));
    });
  };
  // Execução
  fn('.');
  // Apresentação
  return filenames.filter(filename =>
    pattern.filter(aPattern =>
      minimatch(filename, aPattern)
    ).length > 0
  );
};

/**
 * Mescla Elementos de Configuração
 *
 * Recebe como parâmetro um conjunto de objetos que possuem dois parâmetros: `filename` e `content`.
 * Apresenta como resultado os parâmetros `content` mesclados em ordem alfabética de `filename`.
 *
 * @param  {object[]} elements Informações para Mesclagem
 * @return object     Elemento Resultante
 */
Config.prototype.mergeElements = function mergeElements(elements) {
  // Ordenação
  elements.sort((a, b) => a.filename.localeCompare(b.filename));

  // Apresentação
  return elements.reduce((container, item) => merge(container, item.content), {});
};

/**
 * Lê Arquivo de Forma Síncrona
 *
 * Executa a leitura do arquivo informado como parâmetro, apresentando um objeto que representa o
 * seu conteúdo. Caso algum problema seja encontrado, atira um objeto de erro. O objeto apresentado
 * possui dois atributos: `filename` com o valor idêntico ao primeiro parâmetro deste método, e
 * `content` com um objeto que representa o conteúdo JSON do arquivo.
 *
 * @param  {string} filename Nome do Arquivo
 * @throws {Error}  Erro Encontrado durante Processamento
 * @return {object} Objeto que Representa o Conteúdo do Arquivo
 */
Config.prototype.readAndBuildSync = function readAndBuildSync(filename) {
  return { filename, content: JSON.parse(this.getFs().readFileSync(filename)) };
};

/**
 * Busca Configurações
 *
 * @return {Promise} Execução Assíncrona
 */
Config.prototype.fetch = function fetch() {
  // Inicialização
  const self = this;
  const promises = [];

  // Consulta
  this.fetchFilenames().forEach(filename =>
    // Execução Assíncrona
    promises.push(new Promise((resolve, reject) => {
      // Tratamento
      try {
        // Apresentação
        resolve(self.readAndBuildSync(filename));
      } catch (error) {
        // Impossível Continuar
        reject(error);
      }
    }))
  );

  // Execução
  return Promise.all(promises).then(this.mergeElements);
};

/**
 * Busca Configurações de Forma Síncrona
 *
 * @return {object} Conteúdo Solicitado
 */
Config.prototype.fetchSync = function fetchSync() {
  // Execução
  return this.mergeElements(this.fetchFilenames().map(filename => this.readAndBuildSync(filename)));
};

// Configuração
module.exports = Config;
<|endoftext|>


<commit_before>var expect = require('chai').use(require('chai-subset')).expect
var flow = require('../flow')
var PouchDB = require('pouchdb-core')
PouchDB.plugin(require('pouchdb-adapter-memory')).plugin(require('pouchdb-mapreduce'))
var mock = require('mock-fs')
var db
describe('flow', () => {
  beforeEach(() => {
    db = new PouchDB('flow', {
      adapter: 'memory'
    })
    mock({
      '/source/empty': ''
    })
  })
  it('Should add file to db', (done) => {
    flow({
      paths: '/source/**'
    }, db).then(() => db.get('d41d8cd98f00b204e9800998ecf8427e')).then((doc) => {
      expect(doc).to.containSubset({
        'files': {
          '/source/empty': 'PRESENT'
        }
      })
      done()
    }).catch(done)
  })
  afterEach((done) => {
    db.destroy().then(() => done())
    mock.restore()
  })
})
<commit_msg>Test for multiple instances of file<commit_after>var expect = require('chai').use(require('chai-subset')).expect
var flow = require('../flow')
var PouchDB = require('pouchdb-core')
PouchDB.plugin(require('pouchdb-adapter-memory')).plugin(require('pouchdb-mapreduce'))
var mock = require('mock-fs')
var db
describe('flow', () => {
  beforeEach(() => {
    db = new PouchDB('flow', {
      adapter: 'memory'
    })
    mock({
      '/source/empty': '',
      '/source/empty2': ''
    })
  })
  it('Should add file to db', (done) => {
    flow({
      paths: '/source/empty'
    }, db).then(() => db.get('d41d8cd98f00b204e9800998ecf8427e')).then((doc) => {
      expect(doc).to.containSubset({
        'files': {
          '/source/empty': 'PRESENT'
        }
      })
      done()
    }).catch(done)
  })
  it('Should file identical files under the same document', (done) => {
    flow({
      paths: ['/source/empty', '/source/empty2']
    }, db).then(() => db.get('d41d8cd98f00b204e9800998ecf8427e')).then((doc) => {
      expect(doc).to.containSubset({
        'files': {
          '/source/empty': 'PRESENT',
          '/source/empty2': 'PRESENT'
        }
      })
      done()
    }).catch(done)
  })
  afterEach((done) => {
    db.destroy().then(() => done())
    mock.restore()
  })
})
<|endoftext|>