<commit_before>var hat = require('hat');
var Transform = require('stream').Transform;
var EventEmitter = require('events').EventEmitter;

var UserAgent = function(instance, stream) {
  if (!(this instanceof UserAgent)) return new UserAgent(instance, stream);

  this.instance = instance;
  this.backend = instance.backend;

  this.stream = stream;
  this.sessionId = hat();

  this.connectTime = new Date();
};

module.exports = UserAgent;

/**
 * Helper to run the filters over some data. Returns an error string on error,
 * or nothing on success.  Data is modified.
 *
 * Synchronous.
 */
UserAgent.prototype._runFilters = function(filters, collection, docName, data, callback) {
  var i = 0;
  var self = this;
  (function next (err) {
    if (err) return callback(err);
    var filter = filters[i++];
    if (filter) {
      try {
        filter.call(self, collection, docName, data, next);
      } catch (e) {
        console.warn('filter threw an exception. Bailing.');
        console.error(e.stack);
        callback(e.message)
      }
    } else {
      callback(null, data);
    }
  })();
};

UserAgent.prototype.filterDoc = function(collection, docName, data, callback) {
  return this._runFilters(this.instance.docFilters, collection, docName, data, callback);
};
UserAgent.prototype.filterOp = function(collection, docName, data, callback) {
  return this._runFilters(this.instance.opFilters, collection, docName, data, callback);
};

/** Helper to trigger actions */
UserAgent.prototype.trigger = function(action, collection, docName, extraFields, callback) {
  if (typeof extraFields === 'function') {
    callback = extraFields;
    extraFields = {};
  }

  var request = extraFields;
  request.agent = this;
  request.action = action;
  if (collection) request.collection = collection;
  if (docName) request.docName = docName;
  request.backend = this.backend;

  // process.nextTick because the client assumes that it is receiving messages
  // asynchronously and if you have a syncronous stream, we need to force it to
  // be asynchronous
  var instance = this.instance;
  process.nextTick(function() {
    instance._trigger(request, callback);
  });
};

UserAgent.prototype.fetch = function(collection, docName, callback) {
  var agent = this;

  agent.trigger('fetch', collection, docName, function(err, action) {
    if (err) return callback(err);
    collection = action.collection;
    docName = action.docName;

    agent.backend.fetch(collection, docName, function(err, data) {
      if (err) return callback(err);
      if (data) {
        agent.filterDoc(collection, docName, data, callback);
      } else {
        callback(null, data);
      }
    });
  });
};

UserAgent.prototype.getOps = function(collection, docName, start, end, callback) {
  var agent = this;

  agent.trigger('get ops', collection, docName, {start:start, end:end}, function(err, action) {
    if (err) return callback(err);

    agent.backend.getOps(action.collection, action.docName, start, end, function(err, results) {
      if (err) return callback(err);

      if (results) {
        var i = 0;
        (function next (err) {
          if (err) return callback(err);
          var result = results[i++];
          if (result) agent.filterOp(collection, docName, result, next);
          else callback(null, results);
        })();
      } else {
        callback(null, results);
      }
    });
  });
};

/** Helper function to filter & rewrite wrap a stream of operations */
UserAgent.prototype.wrapOpStream = function(collection, docName, stream) {
  var agent = this;
  var passthrough = new Transform({objectMode:true});

  passthrough._transform = function(data, encoding, callback) {
    agent.filterOp(collection, docName, data, function (err, data) {
      passthrough.push(err ? {error: err} : data);
      callback();
    });
  };

  passthrough.destroy = function() { stream.destroy(); };

  stream.pipe(passthrough);

  return passthrough;
};

UserAgent.prototype.subscribe = function(collection, docName, version, callback) {
  var agent = this;
  agent.trigger('subscribe', collection, docName, {version:version}, function(err, action) {
    if (err) return callback(err);
    collection = action.collection;
    docName = agent.docName;
    agent.backend.subscribe(action.collection, action.docName, action.version, function(err, stream) {
       callback(err, err ? err : agent.wrapOpStream(collection, docName, stream));
    });
  });
};

UserAgent.prototype.fetchAndSubscribe = function(collection, docName, callback) {
  var agent = this;
  agent.trigger('fetch', collection, docName, function(err, action) {
    if (err) return callback(err);
    agent.trigger('subscribe', action.collection, action.docName, function(err, action) {
      if (err) return callback(err);

      collection = action.collection;
      docName = agent.docName;
      agent.backend.fetchAndSubscribe(action.collection, action.docName, function(err, data, stream) {
        if (err) return callback(err);
        agent.filterDoc(collection, docName, data, function (err, data) {
          if (err) return callback(err);
          var wrappedStream = agent.wrapOpStream(collection, docName, stream);
          callback(null, data, wrappedStream);
        });
      });
    });
  });
};

UserAgent.prototype.submit = function(collection, docName, opData, options, callback) {
  var agent = this;
  agent.trigger('submit', collection, docName, {opData: opData, channelPrefix:null}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    docName = action.docName;
    opData = action.opData;
    options.channelPrefix = action.channelPrefix;

    if (!opData.preValidate) opData.preValidate = agent.instance.preValidate;
    if (!opData.validate) opData.validate = agent.instance.validate;

    agent.backend.submit(collection, docName, opData, options, function (err, v, ops, snapshot) {
      if (err) return callback(err);
      agent.trigger('after submit', collection, docName, {opData: opData, snapshot: snapshot}, function(err) {
        callback(err, v, ops);
      });
    });
  });
};

/** Helper to filter query result sets */
UserAgent.prototype._filterQueryResults = function(collection, results, callback) {
  if (results.length === 0) return callback(null, results);

  var pending = 0;
  var lastError = null;
  // The filter function is asyncronous. We can run all of the query results in parallel.
  for (var i = 0; i < results.length; i++) {
    var data = results[i];
    this.filterDoc(collection, data.docName, data, function(err) {
      if (err) lastError = err;
      if (++pending === results.length) callback(lastError, results);
    });
  }
};

UserAgent.prototype.queryFetch = function(collection, query, options, callback) {
  var agent = this;
  // Should we emit 'query' or 'query fetch' here?
  agent.trigger('query', collection, null, {query:query, fetch:true, options: options}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    query = action.query;

    agent.backend.queryFetch(collection, query, options, function(err, results, extra) {
      if (err) return callback(err);
      if (results) {
        agent._filterQueryResults(collection, results, function (err, results) {
          if (err) return callback(err);
          callback(null, results, extra);
        });
      } else {
        callback(null, results, extra);
      }
    });
  });
};

UserAgent.prototype.query = function(collection, query, options, callback) {
  var agent = this;
  agent.trigger('query', collection, null, {query:query, options:options}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    query = action.query;

    //console.log('query', query, options);
    agent.backend.query(collection, query, options, function(err, emitter) {
      if (err) return callback(err);
      agent._filterQueryResults(collection, emitter.data, function (err, data) {
        if (err) return callback(err);
        // Wrap the query result event emitter
        var wrapped = new EventEmitter();
        wrapped.data = emitter.data;
        wrapped.extra = emitter.extra; // Can't filter this data. BE CAREFUL!

        wrapped.destroy = function() { emitter.destroy(); };

        emitter.on('diff', function(diff) {
          var i = 0;
          (function next (err) {
            if (err) return wrapped.emit('error', err);
            var curr = diff[i++];
            if (curr) {
              if (curr.type === 'insert') {
                agent._filterQueryResults(collection, curr.values, next);
              } else {
                next();
              }
            } else {
              wrapped.emit('diff', diff);
            }
          })();
        });
        emitter.on('extra', function(extra) {
          wrapped.emit('extra', extra);
        });

        callback(null, wrapped);
      });

    });
  });
};

// 'query',

// filter snapshot
// filter op
// validate new data

<commit_msg>Fixed spacing<commit_after>var hat = require('hat');
var Transform = require('stream').Transform;
var EventEmitter = require('events').EventEmitter;

var UserAgent = function(instance, stream) {
  if (!(this instanceof UserAgent)) return new UserAgent(instance, stream);

  this.instance = instance;
  this.backend = instance.backend;

  this.stream = stream;
  this.sessionId = hat();

  this.connectTime = new Date();
};

module.exports = UserAgent;

/**
 * Helper to run the filters over some data. Returns an error string on error,
 * or nothing on success.  Data is modified.
 *
 * Synchronous.
 */
UserAgent.prototype._runFilters = function(filters, collection, docName, data, callback) {
  var i = 0;
  var self = this;
  (function next(err) {
    if (err) return callback(err);
    var filter = filters[i++];
    if (filter) {
      try {
        filter.call(self, collection, docName, data, next);
      } catch (e) {
        console.warn('filter threw an exception. Bailing.');
        console.error(e.stack);
        callback(e.message)
      }
    } else {
      callback(null, data);
    }
  })();
};

UserAgent.prototype.filterDoc = function(collection, docName, data, callback) {
  return this._runFilters(this.instance.docFilters, collection, docName, data, callback);
};
UserAgent.prototype.filterOp = function(collection, docName, data, callback) {
  return this._runFilters(this.instance.opFilters, collection, docName, data, callback);
};

/** Helper to trigger actions */
UserAgent.prototype.trigger = function(action, collection, docName, extraFields, callback) {
  if (typeof extraFields === 'function') {
    callback = extraFields;
    extraFields = {};
  }

  var request = extraFields;
  request.agent = this;
  request.action = action;
  if (collection) request.collection = collection;
  if (docName) request.docName = docName;
  request.backend = this.backend;

  // process.nextTick because the client assumes that it is receiving messages
  // asynchronously and if you have a syncronous stream, we need to force it to
  // be asynchronous
  var instance = this.instance;
  process.nextTick(function() {
    instance._trigger(request, callback);
  });
};

UserAgent.prototype.fetch = function(collection, docName, callback) {
  var agent = this;

  agent.trigger('fetch', collection, docName, function(err, action) {
    if (err) return callback(err);
    collection = action.collection;
    docName = action.docName;

    agent.backend.fetch(collection, docName, function(err, data) {
      if (err) return callback(err);
      if (data) {
        agent.filterDoc(collection, docName, data, callback);
      } else {
        callback(null, data);
      }
    });
  });
};

UserAgent.prototype.getOps = function(collection, docName, start, end, callback) {
  var agent = this;

  agent.trigger('get ops', collection, docName, {start:start, end:end}, function(err, action) {
    if (err) return callback(err);

    agent.backend.getOps(action.collection, action.docName, start, end, function(err, results) {
      if (err) return callback(err);

      if (results) {
        var i = 0;
        (function next(err) {
          if (err) return callback(err);
          var result = results[i++];
          if (result) agent.filterOp(collection, docName, result, next);
          else callback(null, results);
        })();
      } else {
        callback(null, results);
      }
    });
  });
};

/** Helper function to filter & rewrite wrap a stream of operations */
UserAgent.prototype.wrapOpStream = function(collection, docName, stream) {
  var agent = this;
  var passthrough = new Transform({objectMode:true});

  passthrough._transform = function(data, encoding, callback) {
    agent.filterOp(collection, docName, data, function (err, data) {
      passthrough.push(err ? {error: err} : data);
      callback();
    });
  };

  passthrough.destroy = function() { stream.destroy(); };

  stream.pipe(passthrough);

  return passthrough;
};

UserAgent.prototype.subscribe = function(collection, docName, version, callback) {
  var agent = this;
  agent.trigger('subscribe', collection, docName, {version:version}, function(err, action) {
    if (err) return callback(err);
    collection = action.collection;
    docName = agent.docName;
    agent.backend.subscribe(action.collection, action.docName, action.version, function(err, stream) {
       callback(err, err ? err : agent.wrapOpStream(collection, docName, stream));
    });
  });
};

UserAgent.prototype.fetchAndSubscribe = function(collection, docName, callback) {
  var agent = this;
  agent.trigger('fetch', collection, docName, function(err, action) {
    if (err) return callback(err);
    agent.trigger('subscribe', action.collection, action.docName, function(err, action) {
      if (err) return callback(err);

      collection = action.collection;
      docName = agent.docName;
      agent.backend.fetchAndSubscribe(action.collection, action.docName, function(err, data, stream) {
        if (err) return callback(err);
        agent.filterDoc(collection, docName, data, function (err, data) {
          if (err) return callback(err);
          var wrappedStream = agent.wrapOpStream(collection, docName, stream);
          callback(null, data, wrappedStream);
        });
      });
    });
  });
};

UserAgent.prototype.submit = function(collection, docName, opData, options, callback) {
  var agent = this;
  agent.trigger('submit', collection, docName, {opData: opData, channelPrefix:null}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    docName = action.docName;
    opData = action.opData;
    options.channelPrefix = action.channelPrefix;

    if (!opData.preValidate) opData.preValidate = agent.instance.preValidate;
    if (!opData.validate) opData.validate = agent.instance.validate;

    agent.backend.submit(collection, docName, opData, options, function (err, v, ops, snapshot) {
      if (err) return callback(err);
      agent.trigger('after submit', collection, docName, {opData: opData, snapshot: snapshot}, function(err) {
        callback(err, v, ops);
      });
    });
  });
};

/** Helper to filter query result sets */
UserAgent.prototype._filterQueryResults = function(collection, results, callback) {
  if (results.length === 0) return callback(null, results);

  var pending = 0;
  var lastError = null;
  // The filter function is asyncronous. We can run all of the query results in parallel.
  for (var i = 0; i < results.length; i++) {
    var data = results[i];
    this.filterDoc(collection, data.docName, data, function(err) {
      if (err) lastError = err;
      if (++pending === results.length) callback(lastError, results);
    });
  }
};

UserAgent.prototype.queryFetch = function(collection, query, options, callback) {
  var agent = this;
  // Should we emit 'query' or 'query fetch' here?
  agent.trigger('query', collection, null, {query:query, fetch:true, options: options}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    query = action.query;

    agent.backend.queryFetch(collection, query, options, function(err, results, extra) {
      if (err) return callback(err);
      if (results) {
        agent._filterQueryResults(collection, results, function (err, results) {
          if (err) return callback(err);
          callback(null, results, extra);
        });
      } else {
        callback(null, results, extra);
      }
    });
  });
};

UserAgent.prototype.query = function(collection, query, options, callback) {
  var agent = this;
  agent.trigger('query', collection, null, {query:query, options:options}, function(err, action) {
    if (err) return callback(err);

    collection = action.collection;
    query = action.query;

    //console.log('query', query, options);
    agent.backend.query(collection, query, options, function(err, emitter) {
      if (err) return callback(err);
      agent._filterQueryResults(collection, emitter.data, function (err, data) {
        if (err) return callback(err);
        // Wrap the query result event emitter
        var wrapped = new EventEmitter();
        wrapped.data = emitter.data;
        wrapped.extra = emitter.extra; // Can't filter this data. BE CAREFUL!

        wrapped.destroy = function() { emitter.destroy(); };

        emitter.on('diff', function(diff) {
          var i = 0;
          (function next(err) {
            if (err) return wrapped.emit('error', err);
            var curr = diff[i++];
            if (curr) {
              if (curr.type === 'insert') {
                agent._filterQueryResults(collection, curr.values, next);
              } else {
                next();
              }
            } else {
              wrapped.emit('diff', diff);
            }
          })();
        });
        emitter.on('extra', function(extra) {
          wrapped.emit('extra', extra);
        });

        callback(null, wrapped);
      });

    });
  });
};

// 'query',

// filter snapshot
// filter op
// validate new data

<|endoftext|>


<commit_before>var should = require('should');

var validateArticleApproval = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('version');
    data.version.should.instanceof(String);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('article');
    data.article.should.instanceof(Object);
    validateArticle(data.article);

    data.should.have.property('remark');
    data.remark.should.instanceof(String);

    data.should.have.property('approved');
    data.approved.should.be.Boolean();

    data.should.have.property('rejected');
    data.rejected.should.be.Boolean();

    data.should.have.property('locked');
    data.locked.should.be.Boolean();
};

var validateArticleCategory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleColor = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleCostCalculationDetail = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('article');
    data.article.should.instanceof(Object);
    validateArticle(data.article);

    data.should.have.property('remark');
    data.remark.should.instanceof(String);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();

    data.should.have.property('price');
    data.price.should.be.Number();

    data.should.have.property('total');
    data.total.should.be.Number();

    data.should.have.property('conversionUnit');
    data.conversionUnit.should.instanceof(String);

    data.should.have.property('conversionRate');
    data.conversionRate.should.be.Number();
};

var validateArticleCostCalculation = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);

    data.should.have.property('details');
    data.details.should.instanceof(Array);
    for (var detail of data.details)
        validateArticleCostCalculationDetail(detail);
};

var validateArticleMotif = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleOrigin = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSeason = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSize = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleStyle = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSubCategory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleType = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleVariant = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('size');
    data.size.should.instanceof(String);

    data.should.have.property('color');
    data.color.should.instanceof(String);

    data.should.have.property('picture');
    data.picture.should.instanceof(Array);
};

var validateArticle = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('version');
    data.version.should.instanceof(String);

    data.should.have.property('typeId');
    data.typeId.should.instanceof(Object);

    data.should.have.property('type');
    data.type.should.instanceof(Object);
    validateArticleType(data.type);

    data.should.have.property('originId');
    data.originId.should.instanceof(Object);

    data.should.have.property('origin');
    data.origin.should.instanceof(Object);
    validateArticleOrigin(data.origin);

    data.should.have.property('categoryId');
    data.categoryId.should.instanceof(Object);

    data.should.have.property('category');
    data.category.should.instanceof(Object);
    validateArticleCategory(data.category);

    data.should.have.property('subCategoryId');
    data.subCategoryId.should.instanceof(Object);

    data.should.have.property('subCategory');
    data.subCategory.should.instanceof(Object);
    validateArticleSubCategory(data.subCategory);

    data.should.have.property('styleId');
    data.styleId.should.instanceof(Object);

    data.should.have.property('style');
    data.style.should.instanceof(Object);
    validateArticleStyle(data.style);

    data.should.have.property('seasonId');
    data.seasonId.should.instanceof(Object);

    data.should.have.property('season');
    data.season.should.instanceof(Object);
    validateArticleSeason(data.season);

    data.should.have.property('motifId');
    data.motifId.should.instanceof(Object);

    data.should.have.property('motif');
    data.motif.should.instanceof(Object);
    validateArticleMotif(data.motif);

    data.should.have.property('launch');
    data.launch.should.be.Date();

    data.should.have.property('weight');
    data.weight.should.be.Number();

    data.should.have.property('status');
    data.status.should.instanceof(String);

    data.should.have.property('approved');
    data.approved.should.be.Boolean();

    data.should.have.property('rejected');
    data.rejected.should.be.Boolean();

    data.should.have.property('locked');
    data.locked.should.be.Boolean();

    data.should.have.property('variants');
    data.variants.should.instanceof(Array);
    for (var variant of data.variants)
        validateArticleVariant(variant);
};

exports.article = {
    articleApproval: validateArticleApproval,
    articleCategory: validateArticleCategory,
    articleColor: validateArticleColor,
    articleCostCalculationDetail: validateArticleCostCalculationDetail,
    articleCostCalculation: validateArticleCostCalculation,
    articleMotif: validateArticleMotif,
    articleOrigin: validateArticleOrigin,
    articleSeason: validateArticleSeason,
    articleSize: validateArticleSize,
    articleStyle: validateArticleStyle,
    articleSubCategory: validateArticleSubCategory,
    articleType: validateArticleType,
    articleVariant: validateArticleVariant,
    article: validateArticle
}

var validateInventoryMovement = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateInventory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateStockIn = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('reference');
    data.reference.should.instanceof(String);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('details');
    data.details.should.be.Array();
    for (var item of data.details)
        validateStockItem(item);
};

var validateStockItem = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateStockOut = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('reference');
    data.reference.should.instanceof(String);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('details');
    data.details.should.be.Array();
    for (var item of data.details)
        validateStockItem(item);
};

var validateStorage = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('address');
    data.address.should.instanceof(String);

    data.should.have.property('phone');
    data.phone.should.instanceof(String);
};

exports.inventory = {
    inventoryMovement: validateInventoryMovement,
    inventory: validateInventory,
    stockIn: validateStockIn,
    stockItem: validateStockItem,
    stockOut: validateStockOut,
    storage: validateStorage
}

var validateFinishingItem = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    // data.should.have.property('storageId');
    // data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateFinishing = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    // data.should.have.property('storageId');
    // data.storageId.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.be.String();

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('items');
    data.items.should.instanceof(Array);
    for (var item of data.items)
        validateFinishingItem(item);
};

exports.manufacture = {
    finishingItem: validateFinishingItem,
    finishing: validateFinishing
}<commit_msg>1.0.0<commit_after>var should = require('should');

var validateArticleApproval = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('version');
    data.version.should.instanceof(String);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('article');
    data.article.should.instanceof(Object);
    validateArticle(data.article);

    data.should.have.property('remark');
    data.remark.should.instanceof(String);

    data.should.have.property('approved');
    data.approved.should.be.Boolean();

    data.should.have.property('rejected');
    data.rejected.should.be.Boolean();

    data.should.have.property('locked');
    data.locked.should.be.Boolean();
};

var validateArticleCategory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleColor = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleCostCalculationDetail = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('article');
    data.article.should.instanceof(Object);
    validateArticle(data.article);

    data.should.have.property('remark');
    data.remark.should.instanceof(String);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();

    data.should.have.property('price');
    data.price.should.be.Number();

    data.should.have.property('total');
    data.total.should.be.Number();

    data.should.have.property('conversionUnit');
    data.conversionUnit.should.instanceof(String);

    data.should.have.property('conversionRate');
    data.conversionRate.should.be.Number();
};

var validateArticleCostCalculation = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);

    data.should.have.property('details');
    data.details.should.instanceof(Array);
    for (var detail of data.details)
        validateArticleCostCalculationDetail(detail);
};

var validateArticleMotif = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleOrigin = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSeason = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSize = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleStyle = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleSubCategory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleType = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);
};

var validateArticleVariant = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleId');
    data.articleId.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('description');
    data.description.should.instanceof(String);

    data.should.have.property('size');
    data.size.should.instanceof(String);

    data.should.have.property('color');
    data.color.should.instanceof(String);

    data.should.have.property('picture');
    data.picture.should.instanceof(Array);
};

var validateArticle = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('version');
    data.version.should.instanceof(String);

    data.should.have.property('typeId');
    data.typeId.should.instanceof(Object);

    data.should.have.property('type');
    data.type.should.instanceof(Object);
    validateArticleType(data.type);

    data.should.have.property('originId');
    data.originId.should.instanceof(Object);

    data.should.have.property('origin');
    data.origin.should.instanceof(Object);
    validateArticleOrigin(data.origin);

    data.should.have.property('categoryId');
    data.categoryId.should.instanceof(Object);

    data.should.have.property('category');
    data.category.should.instanceof(Object);
    validateArticleCategory(data.category);

    data.should.have.property('subCategoryId');
    data.subCategoryId.should.instanceof(Object);

    data.should.have.property('subCategory');
    data.subCategory.should.instanceof(Object);
    validateArticleSubCategory(data.subCategory);

    data.should.have.property('styleId');
    data.styleId.should.instanceof(Object);

    data.should.have.property('style');
    data.style.should.instanceof(Object);
    validateArticleStyle(data.style);

    data.should.have.property('seasonId');
    data.seasonId.should.instanceof(Object);

    data.should.have.property('season');
    data.season.should.instanceof(Object);
    validateArticleSeason(data.season);

    data.should.have.property('motifId');
    data.motifId.should.instanceof(Object);

    data.should.have.property('motif');
    data.motif.should.instanceof(Object);
    validateArticleMotif(data.motif);

    data.should.have.property('launch');
    data.launch.should.be.Date();

    data.should.have.property('weight');
    data.weight.should.be.Number();

    data.should.have.property('status');
    data.status.should.instanceof(String);

    data.should.have.property('approved');
    data.approved.should.be.Boolean();

    data.should.have.property('rejected');
    data.rejected.should.be.Boolean();

    data.should.have.property('locked');
    data.locked.should.be.Boolean();

    data.should.have.property('variants');
    data.variants.should.instanceof(Array);
    for (var variant of data.variants)
        validateArticleVariant(variant);
};

exports.article = {
    articleApproval: validateArticleApproval,
    articleCategory: validateArticleCategory,
    articleColor: validateArticleColor,
    articleCostCalculationDetail: validateArticleCostCalculationDetail,
    articleCostCalculation: validateArticleCostCalculation,
    articleMotif: validateArticleMotif,
    articleOrigin: validateArticleOrigin,
    articleSeason: validateArticleSeason,
    articleSize: validateArticleSize,
    articleStyle: validateArticleStyle,
    articleSubCategory: validateArticleSubCategory,
    articleType: validateArticleType,
    articleVariant: validateArticleVariant,
    article: validateArticle
}

var validateInventoryMovement = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateInventory = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateStockIn = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('reference');
    data.reference.should.instanceof(String);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('details');
    data.details.should.be.Array();
    for (var item of data.details)
        validateStockItem(item);
};

var validateStockItem = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateStockOut = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('reference');
    data.reference.should.instanceof(String);

    data.should.have.property('storageId');
    data.storageId.should.instanceof(Object);

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('details');
    data.details.should.be.Array();
    for (var item of data.details)
        validateStockItem(item);
};

var validateStorage = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.instanceof(String);

    data.should.have.property('name');
    data.name.should.instanceof(String);

    data.should.have.property('address');
    data.address.should.instanceof(String);

    data.should.have.property('phone');
    data.phone.should.instanceof(String);
};

exports.inventory = {
    inventoryMovement: validateInventoryMovement,
    inventory: validateInventory,
    stockIn: validateStockIn,
    stockItem: validateStockItem,
    stockOut: validateStockOut,
    storage: validateStorage
}

var validateFinishingItem = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    // data.should.have.property('storageId');
    // data.storageId.should.instanceof(Object);

    data.should.have.property('articleVariantId');
    data.articleVariantId.should.instanceof(Object);

    data.should.have.property('articleVariant');
    data.articleVariant.should.instanceof(Object);
    validateArticleVariant(data.articleVariant);

    data.should.have.property('quantity');
    data.quantity.should.be.Number();
};

var validateFinishing = function(data) {
    data.should.not.equal(null);
    data.should.instanceof(Object);

    // data.should.have.property('storageId');
    // data.storageId.should.instanceof(Object);

    data.should.have.property('code');
    data.code.should.be.String();

    data.should.have.property('date');
    data.date.should.be.Date();

    data.should.have.property('items');
    data.items.should.instanceof(Array);
    for (var item of data.items)
        validateFinishingItem(item);
};

exports.manufacture = {
    finishingItem: validateFinishingItem,
    finishing: validateFinishing
}<|endoftext|>


<commit_before>const mongoose = require('mongoose');
const config = require('../config');
require('../models/coin');
require('../models/walletAddress');
require('../models/wallet');
require('../models/transaction');
require('../models/block');

const StorageService = function(){
};

StorageService.prototype.start = function(ready) {
  mongoose.connect(`mongodb://${config.dbHost}/${config.dbName}?socketTimeoutMS=3600000&noDelay=true`, {
    keepAlive: true,
    poolSize: config.maxPoolSize,
    'native_parser': true
  }, ready);
};

StorageService.prototype.stop = function() {

};

StorageService.prototype.apiStreamingFind = function(model,query,params,res){
  let cursor = model.find(query).cursor({transform: model._apiTransform});
  cursor.on('error', function(err) {
    return res.status(500).end(err.message);
  });
  let isFirst = true;
  res.type('json');
  cursor.on('data', function(data){
    if(isFirst){
      res.write('[\n');
      isFirst = false;
    } else{
      res.write(',\n');
    }
    res.write(data);
  });
  cursor.on('end', function(){
    res.write(']');
    res.end();
  });
};

module.exports = new StorageService();
<commit_msg>Streaming find handles empty data now<commit_after>const mongoose = require('mongoose');
const config = require('../config');
require('../models/coin');
require('../models/walletAddress');
require('../models/wallet');
require('../models/transaction');
require('../models/block');

const StorageService = function(){
};

StorageService.prototype.start = function(ready) {
  mongoose.connect(`mongodb://${config.dbHost}/${config.dbName}?socketTimeoutMS=3600000&noDelay=true`, {
    keepAlive: true,
    poolSize: config.maxPoolSize,
    'native_parser': true
  }, ready);
};

StorageService.prototype.stop = function() {

};

StorageService.prototype.apiStreamingFind = function(model,query,params,res){
  let cursor = model.find(query).cursor({transform: model._apiTransform});
  cursor.on('error', function(err) {
    return res.status(500).end(err.message);
  });
  let isFirst = true;
  res.type('json');
  cursor.on('data', function(data){
    if(isFirst){
      res.write('[\n');
      isFirst = false;
    } else{
      res.write(',\n');
    }
    res.write(data);
  });
  cursor.on('end', function(){
    if(isFirst) {
      // there was no data
      res.write('[]');
    } else {
      res.write(']');
    }
    res.end();
  });
};

module.exports = new StorageService();
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var express = require('express'),
    connect = require('connect');

module.exports = {
    'test #render()': function(assert){
        var app = express.createServer(connect.errorHandler({ showMessage: true }));
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade', { layout: false });
        });
        app.get('/haml', function(req, res){
            res.render('hello.haml', { layout: false });
        });
        app.get('/callback', function(req, res){
            res.render('hello.haml', { layout: false }, function(err, str){
                assert.ok(!err);
                res.send(str.replace('Hello World', ':)'));
            });
        });
        app.get('/invalid', function(req, res){
            res.render('invalid.jade', { layout: false });
        });

        assert.response(app,
            { url: '/' },
            { body: '
Welcome
' });
        assert.response(app,
            { url: '/haml' },
            { body: '\n
Hello World
' });
        assert.response(app,
            { url: '/callback' },
            { body: '\n
:)
' });
        assert.response(app,
            { url: '/invalid' },
            function(res){
                assert.ok(res.body.indexOf('ReferenceError') >= 0);
                assert.ok(res.body.indexOf('doesNotExist') >= 0);
            });
    },

    'test #render() layout': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade');
        });

        assert.response(app,
            { url: '/' },
            { body: '
Welcome
' });
    },

    'test #render() specific layout': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade', { layout: 'cool.layout.jade' });
        });

        assert.response(app,
            { url: '/' },
            { body: '<cool>
Welcome
</cool>' });
    },

    'test #partial()': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        // Auto-assigned local w/ collection option
        app.get('/', function(req, res){
            res.render('items.jade', { locals: { items: ['one', 'two'] }});
        });

       assert.response(app,
            { url: '/' },
            { body: '

one

two

' });

        // Auto-assigned local w/ collection array
        var movies = [
            { title: 'Nightmare Before Christmas', director: 'Tim Burton' },
            { title: 'Avatar', director: 'James Cameron' }
        ];
        app.get('/movies', function(req, res){
            res.render('movies.jade', { locals: { movies: movies }});
        });

        var html = [
            '',
            '',
            '
',
            '
',
            '
Nightmare Before Christmas
',
            '
Tim Burton
',
            '
',
            '
',
            '
Avatar
',
            '
James Cameron
',
            '
',
            '
',
            '',
            ''
        ].join('');

        assert.response(app,
            { url: '/movies' },
            { body: html });

        // as: str collection option
        app.get('/user', function(req, res){
            res.send(res.partial('user.jade', {
                as: 'person',
                collection: [{ name: 'tj' }]
            }));
        });

        assert.response(app,
            { url: '/user' },
            { body: '
tj
' });

        // as: this collection option
        app.get('/person', function(req, res){
            res.send(res.partial('person.jade', {
                as: this,
                collection: [{ name: 'tj' }]
            }));
        });

        assert.response(app,
            { url: '/person' },
            { body: '
tj
' });

        // as: global collection option
        app.get('/videos', function(req, res){
            res.send(res.partial('video.jade', {
                as: global,
                collection: movies
            }));
        });

        assert.response(app,
            { url: '/videos' },
            { body: '
Tim Burton

James Cameron
' });

        // Magic variables
        app.get('/magic', function(req, res){
            res.send(res.partial('magic.jade', {
                as: 'word',
                collection: ['one', 'two', 'three']
            }));
        });

        assert.response(app,
            { url: '/magic' },
            { body: '
one

two

three
' });

        // Non-collection support
        app.get('/video', function(req, res){
            res.send(res.partial('video.jade', {
                locals: { director: movies[0].director }
            }));
        });

        assert.response(app,
            { url: '/video' },
            { body: '
Tim Burton
' });
    }
};<commit_msg>Added view callback error test<commit_after>
/**
 * Module dependencies.
 */

var express = require('express'),
    connect = require('connect');

module.exports = {
    'test #render()': function(assert){
        var app = express.createServer(connect.errorHandler({ showMessage: true }));
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade', { layout: false });
        });
        app.get('/haml', function(req, res){
            res.render('hello.haml', { layout: false });
        });
        app.get('/callback', function(req, res){
            res.render('hello.haml', { layout: false }, function(err, str){
                assert.ok(!err);
                res.send(str.replace('Hello World', ':)'));
            });
        });
        app.get('/invalid', function(req, res){
            res.render('invalid.jade', { layout: false });
        });
        app.get('/error', function(req, res){
            res.render('invalid.jade', { layout: false }, function(err){
                res.send(err.arguments[0]);
            });
        });

        assert.response(app,
            { url: '/' },
            { body: '
Welcome
' });
        assert.response(app,
            { url: '/haml' },
            { body: '\n
Hello World
' });
        assert.response(app,
            { url: '/callback' },
            { body: '\n
:)
' });
        assert.response(app,
            { url: '/error' },
            { body: 'doesNotExist' });
        assert.response(app,
            { url: '/invalid' },
            function(res){
                assert.ok(res.body.indexOf('ReferenceError') >= 0);
                assert.ok(res.body.indexOf('doesNotExist') >= 0);
            });
    },

    'test #render() layout': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade');
        });

        assert.response(app,
            { url: '/' },
            { body: '
Welcome
' });
    },

    'test #render() specific layout': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        app.get('/', function(req, res){
            res.render('index.jade', { layout: 'cool.layout.jade' });
        });

        assert.response(app,
            { url: '/' },
            { body: '<cool>
Welcome
</cool>' });
    },

    'test #partial()': function(assert){
        var app = express.createServer();
        app.set('views', __dirname + '/fixtures');

        // Auto-assigned local w/ collection option
        app.get('/', function(req, res){
            res.render('items.jade', { locals: { items: ['one', 'two'] }});
        });

       assert.response(app,
            { url: '/' },
            { body: '

one

two

' });

        // Auto-assigned local w/ collection array
        var movies = [
            { title: 'Nightmare Before Christmas', director: 'Tim Burton' },
            { title: 'Avatar', director: 'James Cameron' }
        ];
        app.get('/movies', function(req, res){
            res.render('movies.jade', { locals: { movies: movies }});
        });

        var html = [
            '',
            '',
            '
',
            '
',
            '
Nightmare Before Christmas
',
            '
Tim Burton
',
            '
',
            '
',
            '
Avatar
',
            '
James Cameron
',
            '
',
            '
',
            '',
            ''
        ].join('');

        assert.response(app,
            { url: '/movies' },
            { body: html });

        // as: str collection option
        app.get('/user', function(req, res){
            res.send(res.partial('user.jade', {
                as: 'person',
                collection: [{ name: 'tj' }]
            }));
        });

        assert.response(app,
            { url: '/user' },
            { body: '
tj
' });

        // as: this collection option
        app.get('/person', function(req, res){
            res.send(res.partial('person.jade', {
                as: this,
                collection: [{ name: 'tj' }]
            }));
        });

        assert.response(app,
            { url: '/person' },
            { body: '
tj
' });

        // as: global collection option
        app.get('/videos', function(req, res){
            res.send(res.partial('video.jade', {
                as: global,
                collection: movies
            }));
        });

        assert.response(app,
            { url: '/videos' },
            { body: '
Tim Burton

James Cameron
' });

        // Magic variables
        app.get('/magic', function(req, res){
            res.send(res.partial('magic.jade', {
                as: 'word',
                collection: ['one', 'two', 'three']
            }));
        });

        assert.response(app,
            { url: '/magic' },
            { body: '
one

two

three
' });

        // Non-collection support
        app.get('/video', function(req, res){
            res.send(res.partial('video.jade', {
                locals: { director: movies[0].director }
            }));
        });

        assert.response(app,
            { url: '/video' },
            { body: '
Tim Burton
' });
    }
};<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var request = require( "request" ),
    traverse = require( "JSON-object-traverse" );

module.exports = function( options, callback ) {

  function parse( data ) {
    var tempObj,
        oldObj,
        dataArray = [];

    data = data || [];

    // Custom parsing of data so each results mimics results from normal search api.
    dataArray = data.map(function( video ) {
      tempObj = {};
      traverse( video );

      tempObj.source = video.traverse( "link[ 0 ].href", "" );
      tempObj.author = video.traverse( "author[ 0 ].name.$t", "" );
      tempObj.thumbnail = video.traverse( "media$group.media$thumbnail[ 0 ].url", "" );
      tempObj.duration = video.traverse( "media$group.yt$duration.seconds", 0 );
      tempObj.title = video.traverse( "title.$t", "" );
      tempObj.type = "YouTube";

      return tempObj;
    });

    // Results are returned in opposite order as they appear when searching
    // YouTube directly.
    dataArray.reverse();

    return dataArray;
  }

  function getActualTotal( options ) {
    request({
      method: "GET",
      headers: options.headers,
      json: true,
      uri: options.uri.replace( "NEW_INDEX", options.index )
    }, function( err, response, body ) {

      if ( err ) {
        return callback( err );
      }

      if ( !body || !body.feed ) {
        return callback( "[webmaker-mediasync]: No data successfully retrieved when getting total." );
      }

      if ( !body.feed.entry || !body.feed.entry.length ) {
        options.index = options.index - limit;
        return getActualTotal( options );
      }

      var actualTotal = ( options.index + body.feed.entry.length ) - 1;

      options.callback( null, {
        results: options.results,
        total: actualTotal
      });
    });
  }

  var query = options.query.q,
      searchURI = "https://gdata.youtube.com/feeds/api/videos?max-results={LIMIT}&orderby=relevance&alt=json&q={QUERY}",
      headers = {
        "X-GData-Version": 2
      },
      uri = searchURI,
      limit = options.limit,
      page = ( ( options.page - 1 ) * limit ) + 1,
      searchLimit = 1000,
      cachedURI;

  uri = uri.replace( "{QUERY}", query );
  uri = uri.replace( "{LIMIT}", limit );
  cachedURI = uri;
  uri += "&start-index=" + page;

  request({
    method: "GET",
    headers: headers,
    json: true,
    uri: uri
  }, function( err, response, body ) {

    if ( err ) {
      return callback( err );
    }

    if ( !body || !body.feed ) {
      return callback( "[webmaker-mediasync]: No data successfully retrieved." );
    }

    var feed = body.feed,
        data = feed.entry,
        results = parse( data ),
        index = ( searchLimit - limit ) + 1;

    if ( !data || !Array.isArray( data ) || !data.length ) {
      return callback( "[webmaker-mediasync]: No data successfully retrieved." );
    }

    traverse( feed );

    if ( feed.traverse( "openSearch$totalResults.$t", 0 ) === 1 ) {
      return callback( null, {
        results: results,
        total: 1
      });
    }

    // YouTube reports something like 1 million. We limit it to 1000.
    // Even then, it doesn't actually have 1000 results I can get sometimes.
    getActualTotal({
      index: index,
      headers: headers,
      results: results,
      callback: callback,
      uri: cachedURI + "&start-index=NEW_INDEX"
    });
  });
};<commit_msg>No Bug - Fix failing YouTube searches. Lower max limit to a more reliable number<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var request = require( "request" ),
    traverse = require( "JSON-object-traverse" );

module.exports = function( options, callback ) {

  function parse( data ) {
    var tempObj,
        oldObj,
        dataArray = [];

    data = data || [];

    // Custom parsing of data so each results mimics results from normal search api.
    dataArray = data.map(function( video ) {
      tempObj = {};
      traverse( video );

      tempObj.source = video.traverse( "link[ 0 ].href", "" );
      tempObj.author = video.traverse( "author[ 0 ].name.$t", "" );
      tempObj.thumbnail = video.traverse( "media$group.media$thumbnail[ 0 ].url", "" );
      tempObj.duration = video.traverse( "media$group.yt$duration.seconds", 0 );
      tempObj.title = video.traverse( "title.$t", "" );
      tempObj.type = "YouTube";

      return tempObj;
    });

    // Results are returned in opposite order as they appear when searching
    // YouTube directly.
    dataArray.reverse();

    return dataArray;
  }

  function getActualTotal( options ) {
    request({
      method: "GET",
      headers: options.headers,
      json: true,
      uri: options.uri.replace( "NEW_INDEX", options.index )
    }, function( err, response, body ) {

      if ( err ) {
        return callback( err );
      }

      if ( !body || !body.feed ) {
        return callback( "[webmaker-mediasync]: No data successfully retrieved when getting total." );
      }

      if ( !body.feed.entry || !body.feed.entry.length ) {
        options.index = options.index - limit;
        return getActualTotal( options );
      }

      var actualTotal = ( options.index + body.feed.entry.length ) - 1;

      options.callback( null, {
        results: options.results,
        total: actualTotal
      });
    });
  }

  var query = options.query.q,
      searchURI = "https://gdata.youtube.com/feeds/api/videos?max-results={LIMIT}&orderby=relevance&alt=json&q={QUERY}",
      headers = {
        "X-GData-Version": 2
      },
      uri = searchURI,
      limit = options.limit,
      page = ( ( options.page - 1 ) * limit ) + 1,
      searchLimit = 500,
      cachedURI;

  uri = uri.replace( "{QUERY}", query );
  uri = uri.replace( "{LIMIT}", limit );
  cachedURI = uri;
  uri += "&start-index=" + page;

  request({
    method: "GET",
    headers: headers,
    json: true,
    uri: uri
  }, function( err, response, body ) {

    if ( err ) {
      return callback( err );
    }

    if ( !body || !body.feed ) {
      return callback( "[webmaker-mediasync]: No data successfully retrieved." );
    }

    var feed = body.feed,
        data = feed.entry,
        results = parse( data ),
        index = ( searchLimit - limit ) + 1;

    if ( !data || !Array.isArray( data ) || !data.length ) {
      return callback( "[webmaker-mediasync]: No data successfully retrieved." );
    }

    traverse( feed );

    if ( feed.traverse( "openSearch$totalResults.$t", 0 ) === 1 ) {
      return callback( null, {
        results: results,
        total: 1
      });
    }

    // YouTube reports something like 1 million. We limit it to 1000.
    // Even then, it doesn't actually have 1000 results I can get sometimes.
    getActualTotal({
      index: index,
      headers: headers,
      results: results,
      callback: callback,
      uri: cachedURI + "&start-index=NEW_INDEX"
    });
  });
};
<|endoftext|>