<commit_before>/*jslint node: true */
"use strict";

var H = require('../index').EventEmitter;
var _ = require('underscore');
var assert = require('assert');

describe('HevEmitter on', function () {

    describe('explicit promise', function () {

        it('should wait for callback when provided', function (done) {
            var h = new H();
            var wait = true;
            h.on(['james'], function (msg, cb) {});
            h.on(['heroes'], function (msg, cb) {
                return cb();
            });
            h.emit(['james'])
                .then(function () {
                    wait = false;
                });
            h.emit(['heroes'])
                .then(function () {
                    assert(wait);
                    done();
                });
        });

        it('two star error should short circuit one level', function (done) {
            var h = new H();
            var twoStarShort = true;
            h.on(['**'], function (msg, cb) {
                return cb(true);
            });
            h.on(['gross'], function (msg, cb) {
                twoStarShort = false;
                cb();
            });
            h.emit(['gross']).then(function() {
                assert(false);
            })
            .catch(function (err) {
                assert(twoStarShort);
                done();
            });
        });

        it('two star error should short circuit one star', function (done) {
            var h = new H();
            h.on(['**'], function (msg, cb) {
                return cb(true);
            });
            h.on(['*'], function (msg, cb) {
                assert(false);
            });
            h.emit(['gross']).then(function() {
                assert(false);
            })
            .catch(function () {
                done();
            });
        });

        it('one star error should short circuit one level', function (done) {
            var h = new H();
            h.on(['*'], function (msg, cb) {
                return cb(true);
            });
            h.on(['gross'], function (msg, cb) {
                assert(false);
            });
            h.emit(['gross']).then(function() {
                assert(false);
            })
            .catch(function () { done(); });
        });

        it('one level should short circuit one level', function (done) {
            var h = new H();
            h.on(['baby'], function (msg, cb) {
                return cb(true);
            });
            h.on(['baby'], function (msg, cb) {
                assert(false);
            });
            h.emit(['baby']).then(function() {
                assert(false);
            })
            .catch(function () { done(); });
        });

    });

});
<commit_msg>Reorganize short circuit tests.<commit_after>/*jslint node: true */
"use strict";

var H = require('../index').EventEmitter;
var _ = require('underscore');
var assert = require('assert');

describe('HevEmitter on', function () {

    describe('explicit promise', function () {

        it('should wait for callback when provided', function (done) {
            var h = new H();
            var wait = true;
            h.on(['james'], function (msg, cb) {});
            h.on(['heroes'], function (msg, cb) {
                return cb();
            });
            h.emit(['james'])
                .then(function () {
                    wait = false;
                });
            h.emit(['heroes'])
                .then(function () {
                    assert(wait);
                    done();
                });
        });

        it('two star error should short circuit one level', function (done) {
            var h = new H();
            var twoStarShort = true;
            h.on(['**'], function (msg, cb) {
                return cb('block');
            });
            h.on(['gross'], function (msg, cb) {
                twoStarShort = false;
                cb();
            });
            h.emit(['gross'])
            .catch(function (err) {
                assert.equal(err.message, 'block');
                assert(twoStarShort);
                done();
            });
        });

        it('two star error should short circuit one star', function (done) {
            var h = new H();
            var blocked = true;
            h.on(['**'], function (msg, cb) {
                return cb('block');
            });
            h.on(['*'], function (msg, cb) {
                blocked = false;
            });
            h.emit(['gross']).then(function() {
                blocked = false;
            })
            .catch(function (err) {
                assert.equal(err.message, 'block');
                assert(blocked);
                done();
            });
        });

        it('one star error should short circuit one level', function (done) {
            var h = new H();
            var blocked = true;
            h.on(['*'], function (msg, cb) {
                return cb('block');
            });
            h.on(['gross'], function (msg, cb) {
                blocked = false;
            });
            h.emit(['gross']).then(function() {
                blocked = false;
            })
            .catch(function (err) {
                assert.equal(err.message, 'block');
                assert(blocked);
                done();
            });
        });

        it('one level should short circuit one level', function (done) {
            var h = new H();
            var blocked = true;
            h.on(['baby'], function (msg, cb) {
                return cb('block');
            });
            h.on(['baby'], function (msg, cb) {
                blocked = false;
            });
            h.emit(['baby']).then(function() {
                blocked = false;
            })
            .catch(function (err) {
                assert.equal(err.message, 'block');
                assert(blocked);
                done();
            });
        });

        it('one level should short circuit one level even if deferred', function (done) {
            var h = new H();
            var blocked = true;
            h.on(['baby'], function (msg, cb) {
                process.nextTick(function () {
                    cb('block');
                });
            });
            h.on(['baby'], function (msg, cb) {
                blocked = false;
            });
            h.emit(['baby']).then(function() {
                blocked = false;
            })
            .catch(function (err) {
                assert.equal(err.message, 'block');
                assert(blocked);
                done();
            });
        });

    });

});
<|endoftext|>


<commit_before>/**
 * # Widgets
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * Helper class to interact with nodeGame widgets
 */
(function(window, node) {

    "use strict";

    var J = node.JSUS;

    // ## Widgets constructor

    function Widgets() {

        /**
         * ### Widgets.widgets
         *
         * Container of currently registered widgets
         *
         * @see Widgets.register
         */
        this.widgets = {};

        /**
         * ### Widgets.widgets
         *
         * Container of appended widget instances
         *
         * @see Widgets.append
         */
        this.instances = [];
    }

    // ## Widgets methods

    /**
     * ### Widgets.register
     *
     * Registers a new widget in the collection
     *
     * A name and a prototype class must be provided. All properties
     * that are present in `node.Widget`, but missing in the prototype
     * are added.
     *
     * Registered widgets can be loaded with Widgets.get or Widgets.append.
     *
     * @param {string} name The id under which to register the widget
     * @param {function} w The widget to add
     * @return {object|boolean} The registered widget,
     *   or FALSE if an error occurs
     */
    Widgets.prototype.register = function(name, w) {
        var i;
        if ('string' !== typeof name) {
            throw new TypeError('Widgets.register: name must be string.');
        }
        if ('function' !== typeof w) {
            throw new TypeError('Widgets.register: w must be function.');
        }
        // Add default properties to widget prototype
        J.mixout(w.prototype, new node.Widget());
        this.widgets[name] = w;
        return this.widgets[name];
    };

    /**
     * ### Widgets.get
     *
     * Retrieves, instantiates and returns the specified widget
     *
     * It can attach standard javascript listeners to the root element of
     * the widget if specified in the options.
     *
     * The dependencies are checked, and if the conditions are not met,
     * returns FALSE.
     *
     * @param {string} w_str The name of the widget to load
     * @param {options} options Optional. Configuration options
     *   to be passed to the widgets
     *
     * @return {object} widget The requested widget
     *
     * @see Widgets.add
     *
     * @TODO: add supports for any listener. Maybe requires some refactoring.
     * @TODO: add example.
     */
    Widgets.prototype.get = function(w_str, options) {
        var wProto, widget;
        var that;
        if ('string' !== typeof w_str) {
            throw new TypeError('Widgets.get: w_str must be string.');
        }
        if (options && 'object' !== typeof options) {
            throw new TypeError('Widgets.get: options must be object or ' +
                                'undefined.');
        }

        that = this;
        options = options || {};

        wProto = J.getNestedValue(w_str, this.widgets);

        if (!wProto) {
            throw new Error('Widgets.get: ' + w_str + ' not found.');
        }

        node.info('registering ' + wProto.name + ' v.' +  wProto.version);

        if (!this.checkDependencies(wProto)) {
            throw new Error('Widgets.get: ' + w_str + ' has unmet ' +
                            'dependecies.');
        }

        // Add missing properties to the user options
        J.mixout(options, J.clone(wProto.defaults));

        widget = new wProto(options);
        // Re-inject defaults
        widget.defaults = options;

        widget.title = wProto.title;
        widget.footer = wProto.footer;
        widget.className = wProto.className;
        widget.context = wProto.context;

        // Call listeners
        widget.listeners.call(widget);

        // user listeners
        attachListeners(options, widget);

        return widget;
    };

    /**
     * ### Widgets.append
     *
     * Appends a widget to the specified root element
     *
     * If no root element is specified the widget is append to the global root.
     *
     * The first parameter can be string representing the name of the widget or
     * a valid widget already loaded, for example through Widgets.get.
     * In the latter case, dependencies are checked, and it returns FALSE if
     * conditions are not met.
     *
     * It automatically creates a fieldset element around the widget if
     * requested by the internal widget configuration, or if specified in the
     * options parameter.
     *
     * @param {string|object} w The name of the widget to load or a loaded
     *   widget object
     * @param {object} root Optional. The HTML element under which the widget
     *   will be appended. Default: `GameWindow.getFrameRoot()` or
     *   `document.body`
     * @param {options} options Optional. Configuration options to be passed
     *   to the widget
     *
     * @return {object|boolean} The requested widget, or FALSE is an error
     *   occurs
     *
     * @see Widgets.get
     */
    Widgets.prototype.append = Widgets.prototype.add = function(w, root,
                                                                options) {
        if ('string' !== typeof w && 'object' !== typeof w) {
            throw new TypeError('Widgets.append: w must be string or object.');
        }
        if (root && !J.isElement(root)) {
            throw new TypeError('Widgets.append: root must be HTMLElement ' +
                                'or undefined.');
        }
        if (options && 'object' !== typeof options) {
            throw new TypeError('Widgets.append: options must be object or ' +
                                'undefined.');
        }

        // Init default values.
        root = root || W.getFrameRoot() || document.body;
        options = options || {};

        // Check if it is a object (new widget)
        // If it is a string is the name of an existing widget
        // In this case a dependencies check is done
        if ('string' === typeof w) {
            w = this.get(w, options);
        }

        // If fieldset option is null, a div is added instead.
        // If fieldset option is undefined, default options are used.
        //if (options.fieldset !== null) {
        //    root = appendFieldset(root, options.fieldset ||
        //                          w.defaults.fieldset, w);
        //}
        w.panelDiv = appendDiv(root, {
            attributes: {
                className: ['ng_widget', 'panel', 'panel-default', w.className]
            }
        });

        // Optionally add title.
        if (w.title) {
            w.setTitle(w.title);
        }

        // Add body.
        w.bodyDiv = appendDiv(w.panelDiv, {
            attributes: {className: 'panel-body'}
        });

        // Optionally add footer.
        if (w.footer) {
            w.setFooter(w.footer);
        }

        // Optionally set context.
        if (w.context) {
            w.setContext(w.context);
        }

        w.append();

        // Store widget instance for destruction.
        this.instances.push(w);

        return w;
    };

    /**
     * ### Widgets.destroyAll
     *
     * Removes all widgets that have been appended with Widgets.append
     *
     * Exceptions thrown in the widgets' destroy methods are caught.
     *
     * @see Widgets.append
     */
    Widgets.prototype.destroyAll = function() {
        var i, widget;

        for (i in this.instances) {
            if (this.instances.hasOwnProperty(i)) {
                widget = this.instances[i];

                try {
                    widget.destroy();

                    // Remove the widget's div from its parent:
                    widget.panelDiv.parentNode.removeChild(widget.panelDiv);
                }
                catch (e) {
                    node.warn('Widgets.destroyAll: Error caught. ' + e + '.');
                }
            }
        }

        this.instances = [];
    };

    /**
     * ### Widgets.checkDependencies
     *
     * Checks if all the dependencies are already loaded
     *
     * Dependencies are searched for in the following objects:
     *
     * - window
     * - node
     * - this.widgets
     * - node.window
     *
     * TODO: Check for version and other constraints.
     *
     * @param {object} w The widget to check
     * @param {boolean} quiet Optional. If TRUE, no warning will be raised.
     *   Default: FALSE
     * @return {boolean} TRUE, if all dependencies are met
     */
    Widgets.prototype.checkDependencies = function(w, quiet) {
        var parents, d, lib, found, i;
        if (!w.dependencies) return true;

        parents = [window, node, this.widgets, node.window];

        d = w.dependencies;
        for (lib in d) {
            if (d.hasOwnProperty(lib)) {
                found = false;
                for (i = 0; i < parents.length; i++) {
                    if (J.getNestedValue(lib, parents[i])) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    if (!quiet) checkDepErrMsg(w, lib);
                    return false;
                }
            }
        }
        return true;
    };

    // ## Helper functions

    //function appendFieldset(root, options, w) {
    //    var idFieldset, legend;
    //    if (!options) return root;
    //    idFieldset = options.id || w.id + '_fieldset';
    //    legend = options.legend || w.legend;
    //    return W.addFieldset(root, idFieldset, legend, options.attributes);
    //}

    function appendDiv(root, options) {
        // TODO: Check every parameter
        return W.addDiv(root, undefined, options.attributes);
    }

    function createListenerFunction(w, e, l) {
        if (!w || !e || !l) return;
        w.panelDiv[e] = function() {
            l.call(w);
        };
    }

    function attachListeners(options, w) {
        var events, isEvent, i;
        if (!options || !w) return;
        isEvent = false;
        events = ['onclick', 'onfocus', 'onblur', 'onchange',
                  'onsubmit', 'onload', 'onunload', 'onmouseover'];
        for (i in options) {
            if (options.hasOwnProperty(i)) {
                isEvent = J.in_array(i, events);
                if (isEvent && 'function' === typeof options[i]) {
                    createListenerFunction(w, i, options[i]);
                }
            }
        }
    }

    function checkDepErrMsg(w, d) {
        var name = w.name || w.id;// || w.toString();
        node.err(d + ' not found. ' + name + ' cannot be loaded.');
    }

    //Expose Widgets to the global object
    node.widgets = new Widgets();

})(
    // Widgets works only in the browser environment.
    ('undefined' !== typeof window) ? window : module.parent.exports.window,
    ('undefined' !== typeof window) ? window.node : module.parent.exports.node
);
<commit_msg>Removed fieldset from Widgets.append docs<commit_after>/**
 * # Widgets
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * Helper class to interact with nodeGame widgets
 */
(function(window, node) {

    "use strict";

    var J = node.JSUS;

    // ## Widgets constructor

    function Widgets() {

        /**
         * ### Widgets.widgets
         *
         * Container of currently registered widgets
         *
         * @see Widgets.register
         */
        this.widgets = {};

        /**
         * ### Widgets.widgets
         *
         * Container of appended widget instances
         *
         * @see Widgets.append
         */
        this.instances = [];
    }

    // ## Widgets methods

    /**
     * ### Widgets.register
     *
     * Registers a new widget in the collection
     *
     * A name and a prototype class must be provided. All properties
     * that are present in `node.Widget`, but missing in the prototype
     * are added.
     *
     * Registered widgets can be loaded with Widgets.get or Widgets.append.
     *
     * @param {string} name The id under which to register the widget
     * @param {function} w The widget to add
     * @return {object|boolean} The registered widget,
     *   or FALSE if an error occurs
     */
    Widgets.prototype.register = function(name, w) {
        var i;
        if ('string' !== typeof name) {
            throw new TypeError('Widgets.register: name must be string.');
        }
        if ('function' !== typeof w) {
            throw new TypeError('Widgets.register: w must be function.');
        }
        // Add default properties to widget prototype
        J.mixout(w.prototype, new node.Widget());
        this.widgets[name] = w;
        return this.widgets[name];
    };

    /**
     * ### Widgets.get
     *
     * Retrieves, instantiates and returns the specified widget
     *
     * It can attach standard javascript listeners to the root element of
     * the widget if specified in the options.
     *
     * The dependencies are checked, and if the conditions are not met,
     * returns FALSE.
     *
     * @param {string} w_str The name of the widget to load
     * @param {options} options Optional. Configuration options
     *   to be passed to the widgets
     *
     * @return {object} widget The requested widget
     *
     * @see Widgets.add
     *
     * @TODO: add supports for any listener. Maybe requires some refactoring.
     * @TODO: add example.
     */
    Widgets.prototype.get = function(w_str, options) {
        var wProto, widget;
        var that;
        if ('string' !== typeof w_str) {
            throw new TypeError('Widgets.get: w_str must be string.');
        }
        if (options && 'object' !== typeof options) {
            throw new TypeError('Widgets.get: options must be object or ' +
                                'undefined.');
        }

        that = this;
        options = options || {};

        wProto = J.getNestedValue(w_str, this.widgets);

        if (!wProto) {
            throw new Error('Widgets.get: ' + w_str + ' not found.');
        }

        node.info('registering ' + wProto.name + ' v.' +  wProto.version);

        if (!this.checkDependencies(wProto)) {
            throw new Error('Widgets.get: ' + w_str + ' has unmet ' +
                            'dependecies.');
        }

        // Add missing properties to the user options
        J.mixout(options, J.clone(wProto.defaults));

        widget = new wProto(options);
        // Re-inject defaults
        widget.defaults = options;

        widget.title = wProto.title;
        widget.footer = wProto.footer;
        widget.className = wProto.className;
        widget.context = wProto.context;

        // Call listeners
        widget.listeners.call(widget);

        // user listeners
        attachListeners(options, widget);

        return widget;
    };

    /**
     * ### Widgets.append
     *
     * Appends a widget to the specified root element
     *
     * If no root element is specified the widget is append to the global root.
     *
     * The first parameter can be string representing the name of the widget or
     * a valid widget already loaded, for example through Widgets.get.
     * In the latter case, dependencies are checked, and it returns FALSE if
     * conditions are not met.
     *
     * @param {string|object} w The name of the widget to load or a loaded
     *   widget object
     * @param {object} root Optional. The HTML element under which the widget
     *   will be appended. Default: `GameWindow.getFrameRoot()` or
     *   `document.body`
     * @param {options} options Optional. Configuration options to be passed
     *   to the widget
     *
     * @return {object|boolean} The requested widget, or FALSE is an error
     *   occurs
     *
     * @see Widgets.get
     */
    Widgets.prototype.append = Widgets.prototype.add = function(w, root,
                                                                options) {
        if ('string' !== typeof w && 'object' !== typeof w) {
            throw new TypeError('Widgets.append: w must be string or object.');
        }
        if (root && !J.isElement(root)) {
            throw new TypeError('Widgets.append: root must be HTMLElement ' +
                                'or undefined.');
        }
        if (options && 'object' !== typeof options) {
            throw new TypeError('Widgets.append: options must be object or ' +
                                'undefined.');
        }

        // Init default values.
        root = root || W.getFrameRoot() || document.body;
        options = options || {};

        // Check if it is a object (new widget)
        // If it is a string is the name of an existing widget
        // In this case a dependencies check is done
        if ('string' === typeof w) {
            w = this.get(w, options);
        }

        w.panelDiv = appendDiv(root, {
            attributes: {
                className: ['ng_widget', 'panel', 'panel-default', w.className]
            }
        });

        // Optionally add title.
        if (w.title) {
            w.setTitle(w.title);
        }

        // Add body.
        w.bodyDiv = appendDiv(w.panelDiv, {
            attributes: {className: 'panel-body'}
        });

        // Optionally add footer.
        if (w.footer) {
            w.setFooter(w.footer);
        }

        // Optionally set context.
        if (w.context) {
            w.setContext(w.context);
        }

        w.append();

        // Store widget instance for destruction.
        this.instances.push(w);

        return w;
    };

    /**
     * ### Widgets.destroyAll
     *
     * Removes all widgets that have been appended with Widgets.append
     *
     * Exceptions thrown in the widgets' destroy methods are caught.
     *
     * @see Widgets.append
     */
    Widgets.prototype.destroyAll = function() {
        var i, widget;

        for (i in this.instances) {
            if (this.instances.hasOwnProperty(i)) {
                widget = this.instances[i];

                try {
                    widget.destroy();

                    // Remove the widget's div from its parent:
                    widget.panelDiv.parentNode.removeChild(widget.panelDiv);
                }
                catch (e) {
                    node.warn('Widgets.destroyAll: Error caught. ' + e + '.');
                }
            }
        }

        this.instances = [];
    };

    /**
     * ### Widgets.checkDependencies
     *
     * Checks if all the dependencies are already loaded
     *
     * Dependencies are searched for in the following objects:
     *
     * - window
     * - node
     * - this.widgets
     * - node.window
     *
     * TODO: Check for version and other constraints.
     *
     * @param {object} w The widget to check
     * @param {boolean} quiet Optional. If TRUE, no warning will be raised.
     *   Default: FALSE
     * @return {boolean} TRUE, if all dependencies are met
     */
    Widgets.prototype.checkDependencies = function(w, quiet) {
        var parents, d, lib, found, i;
        if (!w.dependencies) return true;

        parents = [window, node, this.widgets, node.window];

        d = w.dependencies;
        for (lib in d) {
            if (d.hasOwnProperty(lib)) {
                found = false;
                for (i = 0; i < parents.length; i++) {
                    if (J.getNestedValue(lib, parents[i])) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    if (!quiet) checkDepErrMsg(w, lib);
                    return false;
                }
            }
        }
        return true;
    };

    // ## Helper functions

    function appendDiv(root, options) {
        // TODO: Check every parameter
        return W.addDiv(root, undefined, options.attributes);
    }

    function createListenerFunction(w, e, l) {
        if (!w || !e || !l) return;
        w.panelDiv[e] = function() {
            l.call(w);
        };
    }

    function attachListeners(options, w) {
        var events, isEvent, i;
        if (!options || !w) return;
        isEvent = false;
        events = ['onclick', 'onfocus', 'onblur', 'onchange',
                  'onsubmit', 'onload', 'onunload', 'onmouseover'];
        for (i in options) {
            if (options.hasOwnProperty(i)) {
                isEvent = J.in_array(i, events);
                if (isEvent && 'function' === typeof options[i]) {
                    createListenerFunction(w, i, options[i]);
                }
            }
        }
    }

    function checkDepErrMsg(w, d) {
        var name = w.name || w.id;// || w.toString();
        node.err(d + ' not found. ' + name + ' cannot be loaded.');
    }

    //Expose Widgets to the global object
    node.widgets = new Widgets();

})(
    // Widgets works only in the browser environment.
    ('undefined' !== typeof window) ? window : module.parent.exports.window,
    ('undefined' !== typeof window) ? window.node : module.parent.exports.node
);
<|endoftext|>


<commit_before>/*
 * grunt-contrib-uglify
 * https://gruntjs.com/
 *
 * Copyright (c) 2013 Small Multiples
 * Licensed under the MIT license.
 */

'use strict';

// External libs.
var  through = require('through')
  , s3sync = require('s3-sync')
  , rimraf = require('rimraf')
  , path = require('path')
  , zlib = require('zlib')
  , url = require('url')
  , fs = require('fs')

module.exports = function(grunt) {
  grunt.registerMultiTask('s3-sync', 'A streaming interface for uploading multiple files to S3.', function() {
    var options = this.options()
      , tmp = path.resolve('.tmp')
      , done = this.async()
      , db = null

    options.headers = options.headers || {}
    if(options.gzip){
      options.headers['Content-Encoding'] = 'gzip'
    }

    if (options.db) {
      db = options.db()
    }

    // Init the stream
    var stream = s3sync(db, options)

    // Log upload message
    stream.on('data', function(file) {
      if (file.cached) {
        grunt.log.ok('[cached] ' + file.url)
      } else
      if (file.fresh) {
        grunt.log.success('>> [uploaded] ' + file.url)
      } else {
        grunt.log.ok('[exists] ' + file.url)
      }

      if (--fileCount > 0) return

      rimraf(tmp, function() {
        stream.end()
        done()
      })
    })

    stream.on('fail', function(err) {
      grunt.log.fail(err.toString())
    })

    var actualFiles = this.files.map(function(set) {
      return set.src.filter(function(file) {
        return grunt.file.isFile(file)
      })
    }).reduce(function(a, b) {
      return a.concat(b)
    }, [])

    var fileCount = actualFiles.length

    // Handle the upload for each files
    var uploadFile = function(src, dest) {
      stream.write({
          src: src
        , dest: dest
      })
    }

    // Upload each file
    this.files.forEach(function(file) {
      file.src.filter(function(file) {
        return actualFiles.indexOf(file) !== -1
      }).forEach(function(src) {
        var absolute = path.resolve(src)
        var dest = url.resolve(file.dest, path.relative(file.root, src))
        var useGzip = 'gzip' in file ? !!file.gzip : !!options.gzip
        console.log('useGzip:', useGzip)
        if (!useGzip) return uploadFile(absolute, dest)

        // GZip the file
        var outputSrc = path.resolve(tmp, src)

        grunt.file.mkdir(path.dirname(outputSrc))

        var gzip = zlib.createGzip()
          , input = fs.createReadStream(absolute)
          , output = fs.createWriteStream(outputSrc)

        input
          .pipe(gzip)
          .pipe(output)
          .once('close', function() {
            uploadFile(outputSrc, dest)
          })
      })
    })
  })
}
<commit_msg>Hash gzipped files properly<commit_after>/*
 * grunt-contrib-uglify
 * https://gruntjs.com/
 *
 * Copyright (c) 2013 Small Multiples
 * Licensed under the MIT license.
 */

'use strict';

// External libs.
var  through = require('through')
  , s3sync = require('s3-sync')
  , rimraf = require('rimraf')
  , path = require('path')
  , zlib = require('zlib')
  , url = require('url')
  , fs = require('fs')

module.exports = function(grunt) {
  grunt.registerMultiTask('s3-sync', 'A streaming interface for uploading multiple files to S3.', function() {
    var options = this.options()
      , tmp = path.resolve('.tmp')
      , done = this.async()
      , db = null

    options.headers = options.headers || {}
    if (options.gzip) {
      options.headers['Content-Encoding'] = 'gzip'
    }

    if (options.db) {
      db = options.db()
    }

    options.hashKey = function(details) {
      return details.orig
    }

    // Init the stream
    var stream = s3sync(db, options)

    // Log upload message
    stream.on('data', function(file) {
      if (file.cached) {
        grunt.log.ok('[cached] ' + file.url)
      } else
      if (file.fresh) {
        grunt.log.success('>> [uploaded] ' + file.url)
      } else {
        grunt.log.ok('[exists] ' + file.url)
      }

      if (--fileCount > 0) return

      rimraf(tmp, function() {
        stream.end()
        done()
      })
    })

    stream.on('fail', function(err) {
      grunt.log.fail(err.toString())
    })

    var actualFiles = this.files.map(function(set) {
      return set.src.filter(function(file) {
        return grunt.file.isFile(file)
      })
    }).reduce(function(a, b) {
      return a.concat(b)
    }, [])

    var fileCount = actualFiles.length

    // Handle the upload for each files
    var uploadFile = function(src, orig, dest) {
      stream.write({
          src: src
        , dest: dest
        , orig: orig
      })
    }

    // Upload each file
    this.files.forEach(function(file) {
      file.src.filter(function(file) {
        return actualFiles.indexOf(file) !== -1
      }).forEach(function(src) {
        var absolute = path.resolve(src)
        var dest = url.resolve(file.dest, path.relative(file.root, src))
        var useGzip = 'gzip' in file ? !!file.gzip : !!options.gzip

        if (!useGzip) return uploadFile(absolute, absolute, dest)

        // GZip the file
        var outputSrc = path.resolve(tmp, src)

        grunt.file.mkdir(path.dirname(outputSrc))

        var gzip = zlib.createGzip()
          , input = fs.createReadStream(absolute)
          , output = fs.createWriteStream(outputSrc)

        input
          .pipe(gzip)
          .pipe(output)
          .once('close', function() {
            uploadFile(outputSrc, absolute, dest)
          })
      })
    })
  })
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Control back end internationalization.
 *
 * @module ov.i18n
 * @main ov.i18n
 */

(function(angular) {
  var app = angular.module('ov.i18n', ['ngCookies']);

  /**
   * Defines an internationalization service to manage string translations.
   *
   * @class i18nService
   */
  function I18nService($http, $cookies) {
    var currentLanguage = $cookies.get('language') || navigator.language || navigator.browserLanguage;
    var translations = {};
    var supportedLanguages = [];

    /**
     * Adds a new dictionary, to the translation table, for the current
     * language.
     * A dictionary is referenced by its name and contains a JSON
     * representation of all its translations.
     * If dictionary does not exist yet, get it from the server.
     *
     * @param {String} name The name of the dictionary to retrieve from
     * server
     * @param {Boolean} [admin] true to retrieve a dictionary for the back
     * office part (which requires authentication), false to get a
     * dictionary without access restriction
     * @return {Promise} The promise to retrieve the dictionary
     * @method addDictionary
     */
    function addDictionary(name, admin) {

      // Dictionary for the currentLanguage
      if (name && (!translations[name] || translations[name][currentLanguage] === undefined)) {
        admin || (admin = false);

        var path = admin ? '/be/' : '/';
        path += 'getDictionary/' + name + '/' + currentLanguage;

        // Get dictionary
        return $http.get(path).success(function(translation) {
          translations[name] || (translations[name] = {});
          translations[name][currentLanguage] = translation;
        }).error(function() {
          translations[name] || (translations[name] = {});
          translations[name][currentLanguage] = null;
        });
      }
    }

    /**
     * Removes a dictionary from cache.
     *
     * @param {String} name The dictionary name
     * @method removeDictionary
     */
    function removeDictionary(name) {
      translations[name] && (delete translations[name]);
    }

    /**
     * Gets a dictionary with all its languages or just for the specific language.
     *
     * @param {String} name The dictionary name
     * @param {String} language An optional language to retrieve
     * @return {Object} The translations contained in the dictionary
     * @method getDictionary
     */
    function getDictionary(name, language) {
      if (language && translations[name])
        return translations[name][language];

      return translations[name];
    }

    /**
     * Gets current language.
     *
     * @return {String} The current language country code (e.g en-US)
     * @method getLanguage
     */
    function getLanguage() {
      return currentLanguage;
    }

    /**
     * Gets supported languages.
     *
     * @return {Array} The list of supported languages
     * @method getLanguages
     */
    function getLanguages() {
      return supportedLanguages;
    }

    /**
     * Tests if a language is supported.
     *
     * @param {String} language The language code to test
     * @return {Boolean} true if supported, false otherwise
     * @method isLanguageSupported
     */
    function isLanguageSupported(language) {
      for (var i = 0; i < supportedLanguages.length; i++) {
        if (language === supportedLanguages[i].value)
          return true;
      }

      return false;
    }

    /**
     * Sets current language to active and the other one to inactive.
     */
    function setActiveLanguage() {
      for (var i = 0; i < supportedLanguages.length; i++)
        supportedLanguages[i].active = supportedLanguages[i].value === currentLanguage;
    }

    /**
     * Sets current language.
     *
     * @param {String} language The current language country code (e.g en-CA)
     * @method setLanguage
     */
    function setLanguage(language) {
      if (isLanguageSupported(language)) {
        currentLanguage = language;
        $cookies.put('language', currentLanguage);
        setActiveLanguage(currentLanguage);
      }
    }

    /**
     * Gets full name of a language by its code.
     *
     * @param {String} language The language code
     * @return {String} The language full name
     * @method getLanguageName
     */
    function getLanguageName(language) {
      for (var i = 0; i < supportedLanguages.length; i++) {
        if (language === supportedLanguages[i].value)
          return supportedLanguages[i].label;
      }

      return null;
    }

    /**
     * Looks for a translation inside a translations object.
     *
     * @example
     *     getTranslationFromDictionary('CORE.HOME.LOGIN', {
     *        'CORE': {
     *          'HOME': {
     *             'LOGIN': 'The translation to look for'
     *           }
     *         }
     *     });
     *
     * @param {String} id The id to retrieve (e.g. CORE.HOME.LOGIN)
     * @param {Object} dictionary Translations where to look for
     * @return {String} The translated text corresponding to the given id
     * in the translations or the unchanged id if no translation found
     */
    function getTranslationFromDictionary(id, dictionary) {
      if (id && dictionary) {
        var properties = id.split('.');
        var property = dictionary;

        for (var i = 0; i < properties.length; i++) {
          if (property[properties[i]])
            property = property[properties[i]];
        }
        return (typeof property === 'string') ? property : id;
      }

      return id;
    }

    /**
     * Translates the given id using current language.
     *
     * @param {String} id The id of the translation
     * @param {String} [dictionary] The name of a particular dictionary if several dictionaries are loaded
     * @method translate
     */
    function translate(id, dictionary) {

      // If the dictionary exists, get translation from
      // that dictionary
      if (dictionary && translations[dictionary]) {

        // Language exists for the dictionary
        if (translations[dictionary][currentLanguage])
          return getTranslationFromDictionary(id, translations[dictionary][currentLanguage]);

        // Language does not exist
        // Use english language as default
        else if (translations[dictionary]['en'])
          return getTranslationFromDictionary(id, translations[dictionary]['en']);

        return id;
      } else {

        // Iterate through the list of dictionaries to find a match
        // for the given id
        var translatedText = id;

        for (var i in translations) {
          translatedText = getTranslationFromDictionary(id, translations[i][currentLanguage]);

          // Language does not exist
          // Use english language as default
          if (translatedText === id)
            translatedText = getTranslationFromDictionary(id, translations[i]['en']);

          if (translatedText !== id)
            break;
          else
            translatedText = id;
        }

        return translatedText;
      }
    }

    /**
     * Destroys I18nService cached data.
     *
     * @method destroy
     */
    function destroy() {
      translations = {};
    }

    /**
     * Initializes supported languages.
     */
    function init() {
      supportedLanguages = [
        {
          value: 'en',
          label: 'CORE.LANGUAGE.ENGLISH'
        },
        {
          value: 'fr',
          label: 'CORE.LANGUAGE.FRENCH'
        }
      ];

      // Verify if current language is supported, otherwise
      // set language to the first one in the list of supported languages
      if (!isLanguageSupported(currentLanguage)) {
        var language = currentLanguage.split('-')[0];
        if (isLanguageSupported(language))
          setLanguage(language);
        else
          setLanguage(supportedLanguages[0].value);
      } else
        setActiveLanguage();
    }

    init();

    return {
      addDictionary: addDictionary,
      removeDictionary: removeDictionary,
      getDictionary: getDictionary,
      getLanguage: getLanguage,
      getLanguages: getLanguages,
      setLanguage: setLanguage,
      translate: translate,
      isLanguageSupported: isLanguageSupported,
      getLanguageName: getLanguageName,
      destroy: destroy
    };
  }

  /**
   * Defines a filter to translate an id, contained inside a dictionary,
   * into the appropriated text.
   *
   * @class translateFilter
   */
  function TranslateFilter(i18nService) {

    /**
     * Translates an id, contained inside a dictionary, into the appropriated text.
     *
     * **translate** filter helps translate a dictionary id.
     *
     * If id is not found in the specified dictionary for the actual language, it will search in the english version
     * of the dictionary.

     * If id is not found in the dictionary, the id is not translated and will be printed as is.

     *
     * If the dictionary is not specified, it will look for the id in all loaded dictionaries.

     * For each dictionary analyzed, if the id does not exist in the actual language it will try in the english
     * version of the dictionary.

     * After all, if id is not found in any versions of the list of dictionaries, the id is not translated and will
     * be printed as is.
     *
     * @example
     *     // Let's pretend that with have a dictionary named "login" with the following structure :
     *     // {
     *     //   "LOGIN" : {
     *     //     "PAGE_TITLE" : "Openveo - Sign In",
     *     //     "DESCRIPTION" : "Login page description",
     *     //     "LOGIN" : "User",
     *     //   }
     *     // }
     *
     *     // Search for id LOGIN.DESCRIPTION in dictionary "login" : "Login page description"
     *
     *     // In HTML
     *     //
     *
     *     // In JavaScript
     *     $filter('translate')('LOGIN.DESCRIPTION', 'login');
     *
     * @param {String} id The id of the translation
     * @param {String} dictionaryName An optional dictionary to prevent looking in all dictionaries
     * @method translate
     */
    return function(id, dictionaryName) {
      return i18nService.translate(id, dictionaryName);
    };

  }

  app.factory('i18nService', I18nService);
  app.filter('translate', TranslateFilter);
  I18nService.$inject = ['$http', '$cookies'];
  TranslateFilter.$inject = ['i18nService'];

})(angular);
<commit_msg>Add the possibility to use variable values within translations<commit_after>'use strict';

/**
 * Control back end internationalization.
 *
 * @module ov.i18n
 * @main ov.i18n
 */

(function(angular) {
  var app = angular.module('ov.i18n', ['ngCookies']);

  /**
   * Defines an internationalization service to manage string translations.
   *
   * @class i18nService
   */
  function I18nService($http, $cookies) {
    var currentLanguage = $cookies.get('language') || navigator.language || navigator.browserLanguage;
    var translations = {};
    var supportedLanguages = [];

    /**
     * Adds a new dictionary, to the translation table, for the current
     * language.
     * A dictionary is referenced by its name and contains a JSON
     * representation of all its translations.
     * If dictionary does not exist yet, get it from the server.
     *
     * @param {String} name The name of the dictionary to retrieve from
     * server
     * @param {Boolean} [admin] true to retrieve a dictionary for the back
     * office part (which requires authentication), false to get a
     * dictionary without access restriction
     * @return {Promise} The promise to retrieve the dictionary
     * @method addDictionary
     */
    function addDictionary(name, admin) {

      // Dictionary for the currentLanguage
      if (name && (!translations[name] || translations[name][currentLanguage] === undefined)) {
        admin || (admin = false);

        var path = admin ? '/be/' : '/';
        path += 'getDictionary/' + name + '/' + currentLanguage;

        // Get dictionary
        return $http.get(path).success(function(translation) {
          translations[name] || (translations[name] = {});
          translations[name][currentLanguage] = translation;
        }).error(function() {
          translations[name] || (translations[name] = {});
          translations[name][currentLanguage] = null;
        });
      }
    }

    /**
     * Removes a dictionary from cache.
     *
     * @param {String} name The dictionary name
     * @method removeDictionary
     */
    function removeDictionary(name) {
      translations[name] && (delete translations[name]);
    }

    /**
     * Gets a dictionary with all its languages or just for the specific language.
     *
     * @param {String} name The dictionary name
     * @param {String} language An optional language to retrieve
     * @return {Object} The translations contained in the dictionary
     * @method getDictionary
     */
    function getDictionary(name, language) {
      if (language && translations[name])
        return translations[name][language];

      return translations[name];
    }

    /**
     * Gets current language.
     *
     * @return {String} The current language country code (e.g en-US)
     * @method getLanguage
     */
    function getLanguage() {
      return currentLanguage;
    }

    /**
     * Gets supported languages.
     *
     * @return {Array} The list of supported languages
     * @method getLanguages
     */
    function getLanguages() {
      return supportedLanguages;
    }

    /**
     * Tests if a language is supported.
     *
     * @param {String} language The language code to test
     * @return {Boolean} true if supported, false otherwise
     * @method isLanguageSupported
     */
    function isLanguageSupported(language) {
      for (var i = 0; i < supportedLanguages.length; i++) {
        if (language === supportedLanguages[i].value)
          return true;
      }

      return false;
    }

    /**
     * Sets current language to active and the other one to inactive.
     */
    function setActiveLanguage() {
      for (var i = 0; i < supportedLanguages.length; i++)
        supportedLanguages[i].active = supportedLanguages[i].value === currentLanguage;
    }

    /**
     * Sets current language.
     *
     * @param {String} language The current language country code (e.g en-CA)
     * @method setLanguage
     */
    function setLanguage(language) {
      if (isLanguageSupported(language)) {
        currentLanguage = language;
        $cookies.put('language', currentLanguage);
        setActiveLanguage(currentLanguage);
      }
    }

    /**
     * Gets full name of a language by its code.
     *
     * @param {String} language The language code
     * @return {String} The language full name
     * @method getLanguageName
     */
    function getLanguageName(language) {
      for (var i = 0; i < supportedLanguages.length; i++) {
        if (language === supportedLanguages[i].value)
          return supportedLanguages[i].label;
      }

      return null;
    }

    /**
     * Looks for a translation inside a translations object.
     *
     * @example
     *     getTranslationFromDictionary('CORE.HOME.LOGIN', {
     *        'CORE': {
     *          'HOME': {
     *             'LOGIN': 'The translation to look for'
     *           }
     *         }
     *     });
     *
     * @param {String} id The id to retrieve (e.g. CORE.HOME.LOGIN)
     * @param {Object} dictionary Translations where to look for
     * @return {String} The translated text corresponding to the given id
     * in the translations or the unchanged id if no translation found
     */
    function getTranslationFromDictionary(id, dictionary) {
      if (id && dictionary) {
        var properties = id.split('.');
        var property = dictionary;

        for (var i = 0; i < properties.length; i++) {
          if (property[properties[i]])
            property = property[properties[i]];
        }
        return (typeof property === 'string') ? property : id;
      }

      return id;
    }

    /**
     * Translates the given id using current language.
     *
     * @param {String} id The id of the translation
     * @param {String} [dictionary] The name of a particular dictionary if several dictionaries are loaded
     * @method translate
     */
    function translate(id, dictionary) {

      // If the dictionary exists, get translation from
      // that dictionary
      if (dictionary && translations[dictionary]) {

        // Language exists for the dictionary
        if (translations[dictionary][currentLanguage])
          return getTranslationFromDictionary(id, translations[dictionary][currentLanguage]);

        // Language does not exist
        // Use english language as default
        else if (translations[dictionary]['en'])
          return getTranslationFromDictionary(id, translations[dictionary]['en']);

        return id;
      } else {

        // Iterate through the list of dictionaries to find a match
        // for the given id
        var translatedText = id;

        for (var i in translations) {
          translatedText = getTranslationFromDictionary(id, translations[i][currentLanguage]);

          // Language does not exist
          // Use english language as default
          if (translatedText === id)
            translatedText = getTranslationFromDictionary(id, translations[i]['en']);

          if (translatedText !== id)
            break;
          else
            translatedText = id;
        }

        return translatedText;
      }
    }

    /**
     * Destroys I18nService cached data.
     *
     * @method destroy
     */
    function destroy() {
      translations = {};
    }

    /**
     * Initializes supported languages.
     */
    function init() {
      supportedLanguages = [
        {
          value: 'en',
          label: 'CORE.LANGUAGE.ENGLISH'
        },
        {
          value: 'fr',
          label: 'CORE.LANGUAGE.FRENCH'
        }
      ];

      // Verify if current language is supported, otherwise
      // set language to the first one in the list of supported languages
      if (!isLanguageSupported(currentLanguage)) {
        var language = currentLanguage.split('-')[0];
        if (isLanguageSupported(language))
          setLanguage(language);
        else
          setLanguage(supportedLanguages[0].value);
      } else
        setActiveLanguage();
    }

    init();

    return {
      addDictionary: addDictionary,
      removeDictionary: removeDictionary,
      getDictionary: getDictionary,
      getLanguage: getLanguage,
      getLanguages: getLanguages,
      setLanguage: setLanguage,
      translate: translate,
      isLanguageSupported: isLanguageSupported,
      getLanguageName: getLanguageName,
      destroy: destroy
    };
  }

  /**
   * Defines a filter to translate an id, contained inside a dictionary,
   * into the appropriated text.
   *
   * @class translateFilter
   */
  function TranslateFilter(i18nService, $parse, $interpolate) {

    /**
     * Translates an id, contained inside a dictionary, into the appropriated text.
     *
     * **translate** filter helps translate a dictionary id.
     *
     * If id is not found in the specified dictionary for the actual language, it will search in the english version
     * of the dictionary.

     * If id is not found in the dictionary, the id is not translated and will be printed as is.

     *
     * If the dictionary is not specified, it will look for the id in all loaded dictionaries.

     * For each dictionary analyzed, if the id does not exist in the actual language it will try in the english
     * version of the dictionary.

     * After all, if id is not found in any versions of the list of dictionaries, the id is not translated and will
     * be printed as is.
     *
     * @example
     *     // Let's pretend that with have a dictionary named "login" with the following structure :
     *     // {
     *     //   "LOGIN" : {
     *     //     "PAGE_TITLE" : "Openveo - Sign In",
     *     //     "DESCRIPTION" : "Login page description",
     *     //     "LOGIN" : "User",
     *     //   }
     *     // }
     *
     *     // Search for id LOGIN.DESCRIPTION in dictionary "login" : "Login page description"
     *
     *     // In HTML
     *     //
     *
     *     // In JavaScript
     *     $filter('translate')('LOGIN.DESCRIPTION', 'login', {name: "John"});
     *
     * @param {String} id The id of the translation
     * @param {String} dictionaryName An optional dictionary to prevent looking in all dictionaries
     * @method translate
     */
    return function(id, dictionaryName, interpolateParams) {
      var translateValue = i18nService.translate(id, dictionaryName),
        exp;

      if (interpolateParams) {
        if (!angular.isObject(interpolateParams)) {
          interpolateParams = $parse(interpolateParams)(this);
        }
        exp = $interpolate(translateValue);
        translateValue = exp(interpolateParams);
      }

      return translateValue;
    };

  }

  app.factory('i18nService', I18nService);
  app.filter('translate', TranslateFilter);
  I18nService.$inject = ['$http', '$cookies'];
  TranslateFilter.$inject = ['i18nService', '$parse', '$interpolate'];

})(angular);
<|endoftext|>


<commit_before>'use strict';

var Imap = require('imap');
var async = require('async');
var MailParser = require('mailparser').MailParser;
var keys = require('./keys.js');
var gmailUrl = require('./lib/provider-gmail/helpers/gmail-url.js');
var xoauth2 = require('xoauth2');
var token;

// Generate a XOAUTH2.0 token
// See https://developers.google.com/gmail/xoauth2_protocol
// And https://github.com/andris9/xoauth2
var xoauth2gen = xoauth2.createXOAuth2Generator({
  user: keys.IMAP_USER,
  clientId: keys.GOOGLE_ID,
  clientSecret: keys.GOOGLE_SECRET,
  refreshToken: keys.GOOGLE_TOKENS.refresh_token
});

async.series([

  function(cb) {
    // Create a token from refresh_token
    xoauth2gen.getToken(function(err, t){
      if(err) {
        return console.log(err);
      }
      token = t;
      cb();
    });
  },
  // Retrieve 10 first messages from the account
  function(cb) {
    // Create a new Imap connection
    // See https://github.com/mscdex/node-imap
    var imap = new Imap({
      xoauth2:token,
      host: 'imap.gmail.com',
      port: 993,
      tls: true,
      tlsOptions: { rejectUnauthorized: false }
    });

    imap.once('ready', function() {
      imap.openBox('[Gmail]/All Mail', true, function(err, box) {
        if (err) {
          throw err;
        }

        console.log(box.messages.total, 'mails in your Gmail.');

        // 1:10 is the span of items to retrieve (first ten items here)
        var f = imap.seq.fetch('1:15', { bodies: ['HEADER.FIELDS (FROM TO CC SUBJECT DATE)', 'TEXT'] });

        f.on('message', function(msg) {

           // Build a buffer containing all datas from the mail
          var buffer = '';
          var id = null;
          var threadId = null;

          msg.on('body', function(stream, info) {
            // Add data to buffer.
            // Headers are added to the top of the buffer,
            // Body after.
            stream.on('data', function(chunk) {
              if(info.which === 'TEXT') {
                buffer += chunk.toString();
              } else {
                buffer = chunk.toString() + buffer;
              }
            });
          });

          msg.once('attributes', function(attrs) {
            id = attrs ['x-gm-msgid'];
            threadId = attrs ['x-gm-thrid'];
          });

          msg.on('end', function() {
            // Create a new parser
            var parser = new MailParser();
            // Normally asynchronous, but we'll feed every data at once so this will be called just after the call to parsen.end().
            parser.on("end", function(mail_object) {
              console.log('----------');
              console.log("Id:", id);
              console.log("tId:", gmailUrl(keys.IMAP_USER, threadId));
              console.log("From:", mail_object.from);
              console.log("To:", mail_object.to);
              console.log("Subject:", mail_object.subject);
              //console.log("Text body:", mail_object.text);
            });

            parser.write(buffer);
            parser.end();
          });
        });

        f.once('error', function(err) {
          console.log('Fetch error: ' + err);
        });
        f.once('end', function() {
          console.log('Done fetching all messages!');
          imap.end();
        });
      });
    });

    imap.once('error', function(err) {
      console.log(err);
    });

    imap.once('end', function(err) {
      if(err) {
        throw err;
      }
      cb();
    });

    imap.connect();
  },
], process.exit);
<commit_msg>Store labels<commit_after>'use strict';

var Imap = require('imap');
var async = require('async');
var MailParser = require('mailparser').MailParser;
var keys = require('./keys.js');
var gmailUrl = require('./lib/provider-gmail/helpers/gmail-url.js');
var xoauth2 = require('xoauth2');
var token;

// Generate a XOAUTH2.0 token
// See https://developers.google.com/gmail/xoauth2_protocol
// And https://github.com/andris9/xoauth2
var xoauth2gen = xoauth2.createXOAuth2Generator({
  user: keys.IMAP_USER,
  clientId: keys.GOOGLE_ID,
  clientSecret: keys.GOOGLE_SECRET,
  refreshToken: keys.GOOGLE_TOKENS.refresh_token
});

async.series([

  function(cb) {
    // Create a token from refresh_token
    xoauth2gen.getToken(function(err, t){
      if(err) {
        return console.log(err);
      }
      token = t;
      cb();
    });
  },
  // Retrieve 10 first messages from the account
  function(cb) {
    // Create a new Imap connection
    // See https://github.com/mscdex/node-imap
    var imap = new Imap({
      xoauth2:token,
      host: 'imap.gmail.com',
      port: 993,
      tls: true,
      tlsOptions: { rejectUnauthorized: false }
    });

    imap.once('ready', function() {
      imap.openBox('[Gmail]/All Mail', true, function(err, box) {
        if (err) {
          throw err;
        }

        console.log(box.messages.total, 'mails in your Gmail.');

        // 1:10 is the span of items to retrieve (first ten items here)
        var f = imap.seq.fetch('1:15', { bodies: ['HEADER.FIELDS (FROM TO CC SUBJECT DATE)', 'TEXT'] });

        f.on('message', function(msg) {

           // Build a buffer containing all datas from the mail
          var buffer = '';
          var attrs = null;

          msg.on('body', function(stream, info) {
            // Add data to buffer.
            // Headers are added to the top of the buffer,
            // Body after.
            stream.on('data', function(chunk) {
              if(info.which === 'TEXT') {
                buffer += chunk.toString();
              } else {
                buffer = chunk.toString() + buffer;
              }
            });
          });

          msg.once('attributes', function(a) {
            attrs = a;
          });

          msg.on('end', function() {
            // Create a new parser
            var parser = new MailParser();
            // Normally asynchronous, but we'll feed every data at once so this will be called just after the call to parsen.end().
            parser.on("end", function(mail_object) {
              console.log('----------');
              console.log("Id:", attrs['x-gm-msgid']);
              console.log("tId:", gmailUrl(keys.IMAP_USER, attrs['x-gm-thrid']));
              console.log("From:", mail_object.from);
              console.log("To:", mail_object.to);
              console.log("Subject:", mail_object.subject);
              console.log("Labels:", attrs['x-gm-labels']);
              //console.log("Text body:", mail_object.text);
            });

            parser.write(buffer);
            parser.end();
          });
        });

        f.once('error', function(err) {
          console.log('Fetch error: ' + err);
        });
        f.once('end', function() {
          console.log('Done fetching all messages!');
          imap.end();
        });
      });
    });

    imap.once('error', function(err) {
      console.log(err);
    });

    imap.once('end', function(err) {
      if(err) {
        throw err;
      }
      cb();
    });

    imap.connect();
  },
], process.exit);
<|endoftext|>


<commit_before>var assert = require('assert')
var sinon = require('sinon')

describe('resourceFilter', function () {
  var ResourceFilter = require('../lib/resource_filter')

  describe('lean', function () {
    var productModel = {
      modelName: 'Product',
      schema: {
        path: sinon.stub().returnsThis()
      }
    }

    var product

    beforeEach(function () {
      product = {
        name: 'Garden Hose',
        price: 50,
        department: {
          name: 'Gardening',
          code: 435,
          manager: 'oid'
        },
        purchases: [{
          customer: 'oid', quantity: 2
        }, {
          customer: 'oid', quantity: 100
        }],
        related: [{
          product: 'oid', name: 'Flowers'
        }, {
          product: 'oid', name: 'Bird'
        }]
      }
    })

    describe('public access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(!filteredProduct.department, 'department should be excluded')
        assert.ok(!filteredProduct.purchases, 'purchases should be excluded')
        assert.ok(!filteredProduct.related, 'related should be excluded')
      })

      it('excludes private and protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(!filteredProduct.department.name, 'department name should be excluded')
        assert.ok(!filteredProduct.department.code, 'department code should be excluded')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(!purchase.customer, 'purchased customer should be excluded')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(!relatedProduct.product, 'related product should be excluded')
        })
      })
    })

    describe('protected access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(!filteredProduct.department, 'department should be excluded')
        assert.ok(!filteredProduct.purchases, 'purchases should be excluded')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(!filteredProduct.department.name, 'department name should be excluded')
        assert.ok(filteredProduct.department.code, 'department code should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(!purchase.customer, 'purchased customer should be excluded')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(relatedProduct.product, 'related product should be included')
        })
      })
    })

    describe('private access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(filteredProduct.department.name, 'department name should be included')
        assert.ok(filteredProduct.department.code, 'department code should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(purchase.customer, 'purchased customer should be included')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(relatedProduct.product, 'related product should be included')
        })
      })
    })

    describe('with populated documents', function () {
      it.skip('excludes private and protected fields from a populated object', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product, {
          populate: [{
            path: 'department'
          }]
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(!filteredProduct.department.name, 'department name should be excluded')
        assert.ok(!filteredProduct.department.code, 'department code should be excluded')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it.skip('excludes private and protected fields from arrays of populated objects', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product, {
          populate: [{
            path: 'purchases'
          }, {
            path: 'related'
          }]
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(purchase.customer, 'purchased customer should be included')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(relatedProduct.product, 'related product should be included')
        })
      })
    })
  })
})
<commit_msg>chore(tests): add cases for populated fields<commit_after>var assert = require('assert')
var sinon = require('sinon')

describe('resourceFilter', function () {
  var ResourceFilter = require('../lib/resource_filter')

  describe('lean', function () {
    var returnFieldRef = function (field) {
      return {
        options: {
          ref: field.charAt(0).toUpperCase() + field.slice(1)
        },
        schema: (function () {
          switch (field) {
            case 'purchases':
              return customerModel.schema
            case 'related':
              return productModel.schema
            default:
              return
          }
        })()
      }
    }

    var customerModel = {
      modelName: 'Customer',
      schema: {
        path: sinon.spy(returnFieldRef)
      }
    }

    var invoiceModel = {
      modelName: 'Invoice',
      schema: {
        path: sinon.spy(returnFieldRef)
      }
    }

    var productModel = {
      modelName: 'Product',
      schema: {
        path: sinon.spy(returnFieldRef)
      }
    }

    var customer, invoice, product

    beforeEach(function () {
      customerModel.schema.path.reset()
      invoiceModel.schema.path.reset()
      productModel.schema.path.reset()

      customer = {
        firstname: 'John',
        lastname: 'Smith',
        purchases: [{
          item: { name: 'Squirt Gun', price: 42 },
          quantity: 2
        }, {
          item: { name: 'Water Balloons', price: 1 },
          quantity: 200
        }, {
          item: { name: 'Garden Hose', price: 10 },
          quantity: 1
        }]
      }

      invoice = {
        customer: 'oid',
        product: ['oid', 'oid'],
        price: 100
      }

      product = {
        name: 'Garden Hose',
        price: 50,
        department: {
          name: 'Gardening',
          code: 435,
          manager: 'Florian'
        },
        purchases: [{
          customer: 'oid', quantity: 2
        }, {
          customer: 'oid', quantity: 100
        }],
        related: [{
          product: 'oid', name: 'Flowers'
        }, {
          product: 'oid', name: 'Bird'
        }]
      }
    })

    describe('public access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(!filteredProduct.department, 'department should be excluded')
        assert.ok(!filteredProduct.purchases, 'purchases should be excluded')
        assert.ok(!filteredProduct.related, 'related should be excluded')
      })

      it('excludes private and protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(!filteredProduct.department.name, 'department name should be excluded')
        assert.ok(!filteredProduct.department.code, 'department code should be excluded')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product)

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(!purchase.customer, 'purchased customer should be excluded')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(!relatedProduct.product, 'related product should be excluded')
        })
      })
    })

    describe('protected access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(!filteredProduct.department, 'department should be excluded')
        assert.ok(!filteredProduct.purchases, 'purchases should be excluded')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(!filteredProduct.department.name, 'department name should be excluded')
        assert.ok(filteredProduct.department.code, 'department code should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('excludes private and includes protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'protected'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(!purchase.customer, 'purchased customer should be excluded')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(relatedProduct.product, 'related product should be included')
        })
      })
    })

    describe('private access', function () {
      it('includes all fields', function () {
        var productFilter = new ResourceFilter(productModel)

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department', 'purchases'],
          protected: ['related']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields from embedded documents', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['department.name'],
          protected: ['department.code']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(filteredProduct.department.manager, 'manager should be included')
        assert.ok(filteredProduct.department.name, 'department name should be included')
        assert.ok(filteredProduct.department.code, 'department code should be included')
        assert.ok(filteredProduct.purchases, 'purchases should be included')
        assert.ok(filteredProduct.related, 'related should be included')
      })

      it('includes private and protected fields from embedded arrays', function () {
        var productFilter = new ResourceFilter(productModel, {
          private: ['purchases.customer'],
          protected: ['related.product']
        })

        var filteredProduct = productFilter.filterObject(product, {
          access: 'private'
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchased quantity should be included')
          assert.ok(purchase.customer, 'purchased customer should be included')
        })
        filteredProduct.related.forEach(function (relatedProduct) {
          assert.ok(relatedProduct.name, 'related name should be included')
          assert.ok(relatedProduct.product, 'related product should be included')
        })
      })
    })

    describe('with populated documents', function () {
      it('excludes private and protected fields from a populated object', function () {
        // Evil side effect
        var customerFilter = new ResourceFilter(customerModel, { // eslint-disable-line no-unused-vars
          private: ['purchases'],
          protected: ['firstname']
        })

        var invoiceFilter = new ResourceFilter(invoiceModel, {
          private: ['product']
        })

        invoice.customer = customer

        var filteredInvoice = invoiceFilter.filterObject(invoice, {
          populate: [{
            path: 'customer'
          }]
        })

        assert.ok(filteredInvoice.price, 'price should be included')
        assert.ok(filteredInvoice.customer, 'customer should be included')
        assert.ok(filteredInvoice.customer.lastname, "customer's lastname should be included")
        assert.ok(!filteredInvoice.product, 'product should be excluded')
        assert.ok(!filteredInvoice.customer.firstname, "customer's firstname should be excluded")
        assert.ok(!filteredInvoice.customer.purchases, "customer's purchases should be excluded")
      })

      it('excludes private and protected fields from arrays of populated object', function () {
        // Evil side effect
        var productFilter = new ResourceFilter(productModel, { // eslint-disable-line no-unused-vars
          private: ['purchases'],
          protected: ['related']
        })

        var invoiceFilter = new ResourceFilter(invoiceModel, {
          private: ['customer']
        })

        invoice.product = [product, product]

        var filteredInvoice = invoiceFilter.filterObject(invoice, {
          populate: [{
            path: 'product'
          }]
        })

        assert.ok(filteredInvoice.price, 'price should be included')
        assert.ok(filteredInvoice.product, 'product should be included')
        assert.ok(!filteredInvoice.customer, 'customer should be excluded')
        filteredInvoice.product.forEach(function (product) {
          assert.ok(product.name, "product's name should be included")
          assert.ok(product.price, "product's price should be included")
          assert.ok(product.department, "product's department should be included")
          assert.ok(!product.purchases, "product's purchases should be excluded")
          assert.ok(!product.related, "product's related should be excluded")
        })
      })

      it('excludes private and protected fields from arrays of nested populated objects', function () {
        // Evil side effect
        var customerFilter = new ResourceFilter(customerModel, { // eslint-disable-line no-unused-vars
          private: ['purchases'],
          protected: ['firstname']
        })

        var productFilter = new ResourceFilter(productModel, {
          private: ['related']
        })

        product.purchases.forEach(function (purchase) {
          purchase.customer = customer
        })

        var filteredProduct = productFilter.filterObject(product, {
          populate: [{
            path: 'purchases.customer'
          }]
        })

        assert.ok(filteredProduct.name, 'name should be included')
        assert.ok(filteredProduct.price, 'price should be included')
        assert.ok(filteredProduct.department, 'department should be included')
        assert.ok(!filteredProduct.related, 'related should be excluded')
        filteredProduct.purchases.forEach(function (purchase) {
          assert.ok(purchase.quantity, 'purchase quantity should be included')
          assert.ok(purchase.customer, 'purchase customer should be included')
          assert.ok(purchase.customer.lastname, 'purchase customer should be included')
          assert.ok(!purchase.customer.firstname, "purchase customer's firstname should be excluded")
          assert.ok(!purchase.customer.purchases, "purchase customer's purchases should be excluded")
        })
      })
    })
  })
})
<|endoftext|>