<commit_before>/**
 * Logger module
 *
 * A Logger helper with fancy colors
 */

var fs           = require('fs'),
    path         = require('path'),
    colors       = require('./colors'),
    errorCodes   = require('./errorCodes'),
    sanitizeCaps = require('../helpers/sanitize').caps;

var Logger = function(options, eventHandler){
    var self = this;

    // log level
    // silent : no logs
    // command : command only
    // result : result only
    // error : error only
    // verbose : command + data + result
    this.logLevel = options.logLevel;

    /**
     * create write stream if logOutput is a string
     */
    if(typeof options.logOutput === 'string') {
        var newDate = new Date(),
            dateString = newDate.toISOString().split(/\./)[0],
            filename = sanitizeCaps(options.desiredCapabilities) + '.' + dateString + '.' + process.pid + '.log';
        this.writeStream = fs.createWriteStream(path.join(process.cwd(), options.logOutput, filename));
        this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel;
    } else if(typeof options.logOutput === 'object' && options.logOutput.writable) {
        this.writeStream = options.logOutput;
        this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel;
    }

    /**
     * disable colors if coloredLogs is set to false or if we pipe output into files
     */
    if(!JSON.parse(process.env.WEBDRIVERIO_COLORED_LOGS) || this.writeStream) {
        Object.keys(colors).forEach(function(colorName) {
            colors[colorName] = '';
        });
    }

    // print welcome message
    if (this.logLevel !== 'silent' && !this.infoHasBeenShown) {
        this.write('\n' + colors.yellow + '=======================================================================================' + colors.reset);
        this.write('Selenium 2.0/webdriver protocol bindings implementation with helper commands in nodejs.');
        this.write('For a complete list of commands, visit ' + colors.lime + 'http://webdriver.io/docs.html' + colors.reset + '. ');
        this.write(colors.yellow + '=======================================================================================' + colors.reset + '\n');
        this.infoHasBeenShown = true;
    }

    // register event handler to log command events
    eventHandler.on('command', function(data) {
        if(self.logLevel === 'command' || self.logLevel === 'verbose') {
            self.command(data.method, data.uri.path);
        }
        if(self.logLevel === 'data' || self.logLevel === 'verbose') {
            self.data(data.data);
        }
    });

    // register event handler to log result events
    eventHandler.on('result', function(data) {
        // only log result events if they got executed successfully
        if(data.body && data.body.status === 0 && (self.logLevel === 'result' || self.logLevel === 'verbose')) {
            self.result(typeof data.body.value ? data.body.value : data.body.orgStatusMessage);
        }
    });

    // register event handler to log error events
    eventHandler.on('error', function(data) {

        if(data.err && data.err.code === 'ECONNREFUSED') {
            self.error('Couldn\'t find a running selenium server instance on ' + data.requestOptions.uri);
        } else if(data.err && data.err.code === 'ENOTFOUND') {
            self.error('Couldn\'t resolve hostname ' + data.requestOptions.uri);
        } else if(data.err && data.err.code === 'NOSESSIONID') {
            self.error('Couldn\'t get a session ID - ' + data.err.message);
        } else if(self.logLevel === 'error' || self.logLevel === 'verbose') {
            if (data.body && errorCodes[data.body.status]) {
                self.error(errorCodes[data.body.status].id + '\t' + errorCodes[data.body.status].message + '\n\t\t\t' + data.body.value.message);
            } else if(typeof data.message === 'string') {
                self.error('ServerError\t' + data.message);
            } else {
                self.error(errorCodes['-1'].id + '\t' + errorCodes['-1'].message);
            }
        }

    });
};

Logger.prototype.write = function(msg) {
    msg = Array.prototype.slice.call(arguments).join(' ');

    if(this.writeStream) {
        this.writeStream.write(msg + '\n');
    } else {
        console.log(msg);
    }
};

/**
 * main log function
 */
Logger.prototype.log = function(message, content) {

    if(!this.logLevel || this.logLevel === 'silent') {
        return;
    }

    var currentDate = new Date();
    var dateString = currentDate.toString().match(/\d\d:\d\d:\d\d/)[0];

    if (!content) {
        this.write(colors.dkgray + '[' + dateString + ']: ' + colors.reset, message);
    } else {
        this.write(colors.dkgray +'[' + dateString + ']: ' + colors.reset, message, '\t', JSON.stringify(content));
    }

};

/**
 * logs command messages
 * @param  {String} method  method of command request
 * @param  {String} path    path of command request
 */
Logger.prototype.command = function(method, path) {
    if(method && path) {
        this.log(colors.violet + 'COMMAND\t' + colors.reset + method, path);
    }
};

Logger.prototype.debug = function() {
    this.write('\n');
    this.log(colors.yellow + 'DEBUG\t' + colors.reset + 'queue has stopped, you can now go into the browser');
    this.log(colors.yellow + 'DEBUG\t' + colors.dkgray + 'continue by pressing the [ENTER] key ...' + colors.reset);
};

/**
 * logs data messages
 * @param  {Object} data  data object
 */
Logger.prototype.data = function(data) {

    data = JSON.stringify(data);

    if(data.length > 1000) {
        data = '[String Buffer]';
    }

    if(data && Object.size(data) !== 0 && (this.logLevel === 'data' || this.logLevel === 'verbose')) {
        this.log(colors.brown + 'DATA\t\t ' + colors.reset + data);
    }
};

/**
 * logs result messages
 * @param  {Object} result  result object
 */
Logger.prototype.result = function (result) {

    // if result is undefined, null or empty don't print it
    if(typeof result === 'undefined' || (typeof result === 'object' && !result) || (typeof result === 'string' && result.length === 0)) {
        return;
    }

    if(typeof result === 'object') {
        result = JSON.stringify(result);
    }

    // prevent screenshot data output
    if(result.length > 1000) {
        result = '[Buffer] screenshot data';
    }

    this.log(colors.teal + 'RESULT\t\t ' + colors.reset + result);
};

/**
 * logs error messages
 * @param  {String} msg  error message
 */
Logger.prototype.error = function(msg) {

    if(msg && typeof msg === 'string' && msg.indexOf('caused by Request') !== -1) {
        msg = msg.substr(0,msg.indexOf('caused by Request') - 2);
    }

    if(msg && typeof msg === 'string' && msg.indexOf('Command duration or timeout') !== -1) {
        msg = msg.substr(0,msg.indexOf('Command duration or timeout'));
    }

    if(msg && typeof msg === 'string' && msg.indexOf('ID does not correspond to an open view') !== -1) {
        msg = msg.substr(0,msg.indexOf('ID does not correspond to an open view'));
        msg += 'NOTE: you probably try to continue your tests after closing a tab/window. Please set the focus on a current opened tab/window to continue. Use the window protocol command to do so.';
    }

    if(msg) {
        this.log(colors.red + 'ERROR\t' + colors.reset + msg, null);
    }
};

/**
 * print exception if command fails
 * @param {String}   type        error type
 * @param {String}   message     error message
 * @param {String[]} stacktrace  error stacktrace
 */
Logger.prototype.printException = function(type, message, stacktrace) {
    stacktrace = stacktrace.map(function(trace) {
        return '    at ' + trace;
    });
    this.write(colors.dkred + (type || 'Error') + ': ' + message + colors.reset, null);
    this.write(colors.dkgray + stacktrace.reverse().join('\n') + colors.reset, null);
};

/**
 * helper method to check size of object
 * @param   {Object}   obj  object you like to check
 * @return  {Integer}       number of own properties
 */
Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

module.exports = Logger;
<commit_msg>we must log something for the Result, even if undefined, or else we won't know we received a result<commit_after>/**
 * Logger module
 *
 * A Logger helper with fancy colors
 */

var fs           = require('fs'),
    path         = require('path'),
    colors       = require('./colors'),
    errorCodes   = require('./errorCodes'),
    sanitizeCaps = require('../helpers/sanitize').caps;

var Logger = function(options, eventHandler){
    var self = this;

    // log level
    // silent : no logs
    // command : command only
    // result : result only
    // error : error only
    // verbose : command + data + result
    this.logLevel = options.logLevel;

    /**
     * create write stream if logOutput is a string
     */
    if(typeof options.logOutput === 'string') {
        var newDate = new Date(),
            dateString = newDate.toISOString().split(/\./)[0],
            filename = sanitizeCaps(options.desiredCapabilities) + '.' + dateString + '.' + process.pid + '.log';
        this.writeStream = fs.createWriteStream(path.join(process.cwd(), options.logOutput, filename));
        this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel;
    } else if(typeof options.logOutput === 'object' && options.logOutput.writable) {
        this.writeStream = options.logOutput;
        this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel;
    }

    /**
     * disable colors if coloredLogs is set to false or if we pipe output into files
     */
    if(!JSON.parse(process.env.WEBDRIVERIO_COLORED_LOGS) || this.writeStream) {
        Object.keys(colors).forEach(function(colorName) {
            colors[colorName] = '';
        });
    }

    // print welcome message
    if (this.logLevel !== 'silent' && !this.infoHasBeenShown) {
        this.write('\n' + colors.yellow + '=======================================================================================' + colors.reset);
        this.write('Selenium 2.0/webdriver protocol bindings implementation with helper commands in nodejs.');
        this.write('For a complete list of commands, visit ' + colors.lime + 'http://webdriver.io/docs.html' + colors.reset + '. ');
        this.write(colors.yellow + '=======================================================================================' + colors.reset + '\n');
        this.infoHasBeenShown = true;
    }

    // register event handler to log command events
    eventHandler.on('command', function(data) {
        if(self.logLevel === 'command' || self.logLevel === 'verbose') {
            self.command(data.method, data.uri.path);
        }
        if(self.logLevel === 'data' || self.logLevel === 'verbose') {
            self.data(data.data);
        }
    });

    // register event handler to log result events
    eventHandler.on('result', function(data) {
        // only log result events if they got executed successfully
        if(data.body && data.body.status === 0 && (self.logLevel === 'result' || self.logLevel === 'verbose')) {
            self.result(typeof data.body.value ? data.body.value : data.body.orgStatusMessage);
        }
    });

    // register event handler to log error events
    eventHandler.on('error', function(data) {

        if(data.err && data.err.code === 'ECONNREFUSED') {
            self.error('Couldn\'t find a running selenium server instance on ' + data.requestOptions.uri);
        } else if(data.err && data.err.code === 'ENOTFOUND') {
            self.error('Couldn\'t resolve hostname ' + data.requestOptions.uri);
        } else if(data.err && data.err.code === 'NOSESSIONID') {
            self.error('Couldn\'t get a session ID - ' + data.err.message);
        } else if(self.logLevel === 'error' || self.logLevel === 'verbose') {
            if (data.body && errorCodes[data.body.status]) {
                self.error(errorCodes[data.body.status].id + '\t' + errorCodes[data.body.status].message + '\n\t\t\t' + data.body.value.message);
            } else if(typeof data.message === 'string') {
                self.error('ServerError\t' + data.message);
            } else {
                self.error(errorCodes['-1'].id + '\t' + errorCodes['-1'].message);
            }
        }

    });
};

Logger.prototype.write = function(msg) {
    msg = Array.prototype.slice.call(arguments).join(' ');

    if(this.writeStream) {
        this.writeStream.write(msg + '\n');
    } else {
        console.log(msg);
    }
};

/**
 * main log function
 */
Logger.prototype.log = function(message, content) {

    if(!this.logLevel || this.logLevel === 'silent') {
        return;
    }

    var currentDate = new Date();
    var dateString = currentDate.toString().match(/\d\d:\d\d:\d\d/)[0];

    if (!content) {
        this.write(colors.dkgray + '[' + dateString + ']: ' + colors.reset, message);
    } else {
        this.write(colors.dkgray +'[' + dateString + ']: ' + colors.reset, message, '\t', JSON.stringify(content));
    }

};

/**
 * logs command messages
 * @param  {String} method  method of command request
 * @param  {String} path    path of command request
 */
Logger.prototype.command = function(method, path) {
    if(method && path) {
        this.log(colors.violet + 'COMMAND\t' + colors.reset + method, path);
    }
};

Logger.prototype.debug = function() {
    this.write('\n');
    this.log(colors.yellow + 'DEBUG\t' + colors.reset + 'queue has stopped, you can now go into the browser');
    this.log(colors.yellow + 'DEBUG\t' + colors.dkgray + 'continue by pressing the [ENTER] key ...' + colors.reset);
};

/**
 * logs data messages
 * @param  {Object} data  data object
 */
Logger.prototype.data = function(data) {

    data = JSON.stringify(data);

    if(data.length > 1000) {
        data = '[String Buffer]';
    }

    if(data && Object.size(data) !== 0 && (this.logLevel === 'data' || this.logLevel === 'verbose')) {
        this.log(colors.brown + 'DATA\t\t ' + colors.reset + data);
    }
};

/**
 * logs result messages
 * @param  {Object} result  result object
 */
Logger.prototype.result = function (result) {

    if(typeof result === 'object') {
        result = JSON.stringify(result);
    }

    // prevent screenshot data output
    if(result.length > 1000) {
        result = '[Buffer] screenshot data';
    }

    this.log(colors.teal + 'RESULT\t\t ' + colors.reset + result);
};

/**
 * logs error messages
 * @param  {String} msg  error message
 */
Logger.prototype.error = function(msg) {

    if(msg && typeof msg === 'string' && msg.indexOf('caused by Request') !== -1) {
        msg = msg.substr(0,msg.indexOf('caused by Request') - 2);
    }

    if(msg && typeof msg === 'string' && msg.indexOf('Command duration or timeout') !== -1) {
        msg = msg.substr(0,msg.indexOf('Command duration or timeout'));
    }

    if(msg && typeof msg === 'string' && msg.indexOf('ID does not correspond to an open view') !== -1) {
        msg = msg.substr(0,msg.indexOf('ID does not correspond to an open view'));
        msg += 'NOTE: you probably try to continue your tests after closing a tab/window. Please set the focus on a current opened tab/window to continue. Use the window protocol command to do so.';
    }

    if(msg) {
        this.log(colors.red + 'ERROR\t' + colors.reset + msg, null);
    }
};

/**
 * print exception if command fails
 * @param {String}   type        error type
 * @param {String}   message     error message
 * @param {String[]} stacktrace  error stacktrace
 */
Logger.prototype.printException = function(type, message, stacktrace) {
    stacktrace = stacktrace.map(function(trace) {
        return '    at ' + trace;
    });
    this.write(colors.dkred + (type || 'Error') + ': ' + message + colors.reset, null);
    this.write(colors.dkgray + stacktrace.reverse().join('\n') + colors.reset, null);
};

/**
 * helper method to check size of object
 * @param   {Object}   obj  object you like to check
 * @return  {Integer}       number of own properties
 */
Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

module.exports = Logger;
<|endoftext|>


<commit_before>/* eslint-env mocha */
/* global Command, ValidationError, OptionError, MissingOptionError, InvalidOptionError, Runner, API, CLI */
'use strict'
var _ = require('lodash')
var chai = require('chai')
var final = require('..')
var fs = require('fs')
var http = require('http')
var path = require('path')
var request = require('supertest')
var sinon = require('sinon')
var sinonChai = require('sinon-chai')
var url = require('url')

var expect = chai.expect
chai.use(sinonChai)
Object.assign(global, final)

describe('final', () => {
  var commandCore = (options) => {
    return _.parseInt(options.first) + (_.parseInt(options.second) || 0)
  }

  var commandOptions = {
    first: {
      description: 'first number to add',
      required: true
    },
    second: {
      description: 'second number to add'
    }
  }

  var command = new Command(commandCore, commandOptions)

  var options = { first: 1, second: 2 }
  var stringOptions = _.mapValues(options, String)

  var invalidOptionError = new InvalidOptionError('invalid')
  var missingOptionError = new MissingOptionError('missing')
  var optionErrors = new Set([invalidOptionError, missingOptionError])
  var validationError = new ValidationError(optionErrors)

  var sandbox
  beforeEach(() => { sandbox = sinon.sandbox.create() })
  afterEach(() => { sandbox.restore() })

  describe('ValidationError', () => {
    describe('constructor', () => {
      it('sets name to ValidationError', () => {
        expect(validationError.name).to.equal('ValidationError')
      })

      it('sets optionErrors', () => {
        expect(validationError.optionErrors).to.equal(optionErrors)
      })
    })

    describe('#mapOptionErrors()', () => {
      it('maps over its optionErrors', () => {
        expect(validationError.mapOptionErrors((e) => e.option)).to.deep.equal(['invalid', 'missing'])
      })
    })

    describe('#toJSON()', () => {
      it('returns a JSON representation of itself, including its optionErrors, as an Object', () => {
        expect(validationError.toJSON()).to.deep.equal({ errors: [{ name: 'InvalidOptionError', option: 'invalid' }, { name: 'MissingOptionError', option: 'missing' }] })
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of its optionErrors', () => {
        expect(validationError.toText()).to.equal('Error: Invalid option "invalid"\nError: Missing required option "missing"')
      })
    })
  })

  describe('OptionError', () => {
    var optionError = new OptionError('option')

    describe('constructor', () => {
      it('sets name to OptionError', () => {
        expect(optionError.name).to.equal('OptionError')
      })

      it('sets option', () => {
        expect(optionError.option).to.equal('option')
      })
    })

    describe('#toJSON()', () => {
      it('returns a JSON representation of the error\'s name and option as an Object', () => {
        expect(optionError.toJSON()).to.deep.equal({ name: 'OptionError', option: 'option' })
      })
    })
  })

  describe('InvalidOptionError', () => {
    describe('constructor', () => {
      it('sets name to InvalidOptionError', () => {
        expect(invalidOptionError.name).to.equal('InvalidOptionError')
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of itself', () => {
        expect(invalidOptionError.toText()).to.equal('Error: Invalid option "invalid"')
      })
    })
  })

  describe('MissingOptionError', () => {
    describe('constructor', () => {
      it('sets name to MissingOptionError', () => {
        expect(missingOptionError.name).to.equal('MissingOptionError')
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of itself', () => {
        expect(missingOptionError.toText()).to.equal('Error: Missing required option "missing"')
      })
    })
  })

  describe('Command', () => {
    var greeting = 'Hello, world!'
    var simpleCommandCore = () => greeting
    var simpleCommand = new Command(simpleCommandCore)

    describe('constructor', () => {
      context('for a command without options', () => {
        it('uses the given core', () => {
          expect(simpleCommand.core).to.equal(simpleCommandCore)
        })

        it('doesn\'t use any options', () => {
          expect(simpleCommand.options).to.be.undefined
        })
      })

      context('for a command with required and optional options', () => {
        it('uses the given core', () => {
          expect(command.core).to.equal(commandCore)
        })

        it('uses the given options', () => {
          expect(command.options).to.equal(commandOptions)
        })
      })
    })

    describe('#createErrors()', () => {
      it('creates errors of the given class for the given options', () => {
        expect(Command.createErrors(OptionError, ['one', 'two'])).to.deep.equal([new OptionError('one'), new OptionError('two')])
      })
    })

    describe('#difference()', () => {
      it('returns the difference of two Sets', () => {
        expect(Command.difference(new Set([1, 2]), new Set([2, 3]))).to.deep.equal(new Set([1]))
      })
    })

    describe('#getOptionNames()', () => {
      it('returns the names of the given options Object', () => {
        expect(Command.getOptionNames(commandOptions)).to.deep.equal(new Set(['first', 'second']))
      })
    })

    describe('#run()', () => {
      context('for a command without options', () => {
        context('given empty options', () => {
          it('returns a String result', () => {
            expect(simpleCommand.run({})).to.equal(greeting)
          })
        })

        context('given any option', () => {
          it('returns a String result', () => {
            expect(simpleCommand.run({ extra: true })).to.equal(greeting)
          })
        })
      })

      context('for a command with required and optional options', () => {
        context('given empty options', () => {
          it('throws a ValidationError', () => {
            expect(() => command.run({})).to.throw(ValidationError)
          })
        })

        context('given only the required option', () => {
          it('returns a String result', () => {
            expect(command.run({ first: 1 })).to.equal('1')
          })
        })

        context('given only the optional option', () => {
          it('throws a ValidationError', () => {
            expect(
              () => command.run({ second: 2 })
            ).to.throw(ValidationError)
          })
        })

        context('given both options', () => {
          it('returns a String result', () => {
            expect(command.run(options)).to.equal('3')
          })
        })

        context('given an invalid option', () => {
          it('throws a ValidationError', () => {
            expect(
              () => command.run({ first: 1, invalid: true })
            ).to.throw(ValidationError)
          })
        })
      })
    })

    describe('#validate()', () => {
      function expectValidationErrors (command, options, expected) {
        var errors = command.validate(new Set(options))
        expect(errors).to.deep.equal(new Set(expected))
      }

      context('for a command with no options', () => {
        context('given no options', () => {
          it('returns no errors', () => {
            expectValidationErrors(simpleCommand, [], [])
          })
        })

        context('given an option', () => {
          it('returns no errors', () => {
            expectValidationErrors(simpleCommand, ['extra'], [])
          })
        })
      })

      context('for a command with a required option and an optional option', () => {
        context('given no options', () => {
          it('returns a MissingOptionError', () => {
            expectValidationErrors(command, [], [
              new MissingOptionError('first')
            ])
          })
        })

        context('given the required option', () => {
          it('returns no errors', () => {
            expectValidationErrors(command, ['first'], [])
          })
        })

        context('given the optional option', () => {
          it('returns a MissingOptionError', () => {
            expectValidationErrors(command, ['second'], [
              new MissingOptionError('first')
            ])
          })
        })

        context('given an invalid option', () => {
          it('returns a MissingOptionError and an InvalidOptionError', () => {
            expectValidationErrors(command, ['invalid'], [
              new MissingOptionError('first'),
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the required option and the optional option', () => {
          it('returns no errors', () => {
            expectValidationErrors(command, ['first', 'second'], [])
          })
        })

        context('given the required option and an invalid option', () => {
          it('returns an InvalidOptionError', () => {
            expectValidationErrors(command, ['first', 'invalid'], [
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the optional option and an invalid option', () => {
          it('returns a MissingOptionError and an InvalidOptionError', () => {
            expectValidationErrors(command, ['second', 'invalid'], [
              new MissingOptionError('first'),
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the required option, the optional option, and an invalid option', () => {
          it('returns an InvalidOptionError', () => {
            expectValidationErrors(command, ['first', 'second', 'invalid'], [
              new InvalidOptionError('invalid')
            ])
          })
        })
      })
    })
  })

  describe('Runner', () => {
    describe('constructor', () => {
      it('uses the given command', () => {
        expect(new Runner(command).command).to.equal(command)
      })
    })
  })

  describe('API', () => {
    var api = new API(command)

    var req = new http.IncomingMessage()
    req.url = 'http://localhost:3000?first=1&second=2'
    var parsedReq = url.parse(req.url)

    after(() => api.close())

    describe('constructor', () => {
      it('creates a server', () => {
        expect(api.server).to.be.an.instanceof(http.Server)
      })
    })

    describe('#callback()', () => {
      it('is a function', () => {
        expect(api.callback).to.be.an.instanceof(Function)
      })

      it('takes a request and a response', () => {
        expect(api.callback).to.have.length(2)
      })
    })

    describe('#server', () => {
      context('given valid options', () => {
        it('responds with a result', (done) => {
          request(api.server)
            .get(parsedReq.path)
            .expect(200, '3\n')
            .expect('content-type', 'text/plain')
            .end(done)
        })
      })

      context('given invalid options', () => {
        it('responds with validation errors', (done) => {
          request(api.server)
            .get('?invalid')
            .expect(403, {
              errors: [
                {
                  name: 'MissingOptionError',
                  option: 'first'
                },
                {
                  name: 'InvalidOptionError',
                  option: 'invalid'
                }
              ]
            })
            .expect('content-type', 'application/json')
            .end(done)
        })
      })
    })

    describe('#callback()', () => {
      it('is a function', () => {
        expect(api.callback instanceof Function).to.be.true
      })

      it('takes a request and a response', () => {
        expect(api.callback.length).to.equal(2)
      })
    })

    describe('#close()', () => {
      it('closes the server', (done) => {
        api.close()

        request(parsedReq.host)
          .get(parsedReq.path)
          .end((err) => {
            err ? done() : done('Error: API server should be closed')
          })
      })
    })

    describe('.options()', () => {
      it('returns options from the given request', () => {
        expect(API.options(req)).to.deep.equal(stringOptions)
      })
    })

    describe('#run()', () => {
      it('runs its server for the given command', (done) => {
        api.run()

        request(parsedReq.host)
          .get(parsedReq.path)
          .end(done)
      })
    })
  })

  describe('CLI', () => {
    var args
    var cli = new CLI(command)

    before(() => { args = 'node cli.js --first 1 --second 2' })
    beforeEach(() => { process.argv = args.split(' ') })

    describe('#formatOptions()', () => {
      it('formats the given options for usage information', () => {
        expect(CLI.formatOptions(commandOptions)).to.equal('  --first              first number to add\n  --second             second number to add')
      })
    })

    describe('#help()', () => {
      it('returns formatted help text', () => {
        var helpTextPath = path.join(__dirname, 'help.txt')
        var helpText = fs.readFileSync(helpTextPath, 'utf-8').trim()
        expect(cli.help()).to.equal(helpText)
      })
    })

    describe('.options()', () => {
      it('returns options from argv', () => {
        expect(CLI.options()).to.deep.equal(options)
      })
    })

    describe('#run()', () => {
      beforeEach(() => { sandbox.spy(cli, 'help') })

      // Sets up stubs for tests that would produce console output. This can't
      // be in a beforeEach() hook because the console stubs would hide mocha's
      // command line results.
      function stubOutput (callback) {
        return () => {
          sandbox.stub(console, 'error')
          sandbox.stub(console, 'log')
          cli.run()
          callback()
        }
      }

      context('given valid options', () => {
        it('runs a cli for the given command that prints a result', stubOutput(function () {
          expect(cli.help).to.not.have.been.called
          expect(console.error).to.not.have.been.called
          expect(console.log).to.have.been.calledOnce
          expect(console.log).to.have.been.calledWithExactly('3')
        }))
      })

      function itDisplaysHelp () {
        it('displays help', stubOutput(function () {
          expect(cli.help).to.have.been.calledOnce
          expect(cli.help).to.have.been.calledWithExactly()
        }))
      }

      context('given invalid options', () => {
        before(() => { args = 'node cli.js --invalid' })

        it('displays validation errors and help', stubOutput(() => {
          var expected = 'Error: Missing required option "first"\n' +
                         'Error: Invalid option "invalid"'
          expect(console.error).to.have.been.calledOnce
          expect(console.error).to.have.been.calledWithExactly(expected)
          expect(console.log).to.have.been.called
        }))

        itDisplaysHelp()
      })

      context('given the help flag', () => {
        before(() => { args = 'node cli.js --help' })

        itDisplaysHelp()
      })
    })
  })
})
<commit_msg>Use the instanceof matcher<commit_after>/* eslint-env mocha */
/* global Command, ValidationError, OptionError, MissingOptionError, InvalidOptionError, Runner, API, CLI */
'use strict'
var _ = require('lodash')
var chai = require('chai')
var final = require('..')
var fs = require('fs')
var http = require('http')
var path = require('path')
var request = require('supertest')
var sinon = require('sinon')
var sinonChai = require('sinon-chai')
var url = require('url')

var expect = chai.expect
chai.use(sinonChai)
Object.assign(global, final)

describe('final', () => {
  var commandCore = (options) => {
    return _.parseInt(options.first) + (_.parseInt(options.second) || 0)
  }

  var commandOptions = {
    first: {
      description: 'first number to add',
      required: true
    },
    second: {
      description: 'second number to add'
    }
  }

  var command = new Command(commandCore, commandOptions)

  var options = { first: 1, second: 2 }
  var stringOptions = _.mapValues(options, String)

  var invalidOptionError = new InvalidOptionError('invalid')
  var missingOptionError = new MissingOptionError('missing')
  var optionErrors = new Set([invalidOptionError, missingOptionError])
  var validationError = new ValidationError(optionErrors)

  var sandbox
  beforeEach(() => { sandbox = sinon.sandbox.create() })
  afterEach(() => { sandbox.restore() })

  describe('ValidationError', () => {
    describe('constructor', () => {
      it('sets name to ValidationError', () => {
        expect(validationError.name).to.equal('ValidationError')
      })

      it('sets optionErrors', () => {
        expect(validationError.optionErrors).to.equal(optionErrors)
      })
    })

    describe('#mapOptionErrors()', () => {
      it('maps over its optionErrors', () => {
        expect(validationError.mapOptionErrors((e) => e.option)).to.deep.equal(['invalid', 'missing'])
      })
    })

    describe('#toJSON()', () => {
      it('returns a JSON representation of itself, including its optionErrors, as an Object', () => {
        expect(validationError.toJSON()).to.deep.equal({ errors: [{ name: 'InvalidOptionError', option: 'invalid' }, { name: 'MissingOptionError', option: 'missing' }] })
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of its optionErrors', () => {
        expect(validationError.toText()).to.equal('Error: Invalid option "invalid"\nError: Missing required option "missing"')
      })
    })
  })

  describe('OptionError', () => {
    var optionError = new OptionError('option')

    describe('constructor', () => {
      it('sets name to OptionError', () => {
        expect(optionError.name).to.equal('OptionError')
      })

      it('sets option', () => {
        expect(optionError.option).to.equal('option')
      })
    })

    describe('#toJSON()', () => {
      it('returns a JSON representation of the error\'s name and option as an Object', () => {
        expect(optionError.toJSON()).to.deep.equal({ name: 'OptionError', option: 'option' })
      })
    })
  })

  describe('InvalidOptionError', () => {
    describe('constructor', () => {
      it('sets name to InvalidOptionError', () => {
        expect(invalidOptionError.name).to.equal('InvalidOptionError')
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of itself', () => {
        expect(invalidOptionError.toText()).to.equal('Error: Invalid option "invalid"')
      })
    })
  })

  describe('MissingOptionError', () => {
    describe('constructor', () => {
      it('sets name to MissingOptionError', () => {
        expect(missingOptionError.name).to.equal('MissingOptionError')
      })
    })

    describe('#toText()', () => {
      it('returns a textual representation of itself', () => {
        expect(missingOptionError.toText()).to.equal('Error: Missing required option "missing"')
      })
    })
  })

  describe('Command', () => {
    var greeting = 'Hello, world!'
    var simpleCommandCore = () => greeting
    var simpleCommand = new Command(simpleCommandCore)

    describe('constructor', () => {
      context('for a command without options', () => {
        it('uses the given core', () => {
          expect(simpleCommand.core).to.equal(simpleCommandCore)
        })

        it('doesn\'t use any options', () => {
          expect(simpleCommand.options).to.be.undefined
        })
      })

      context('for a command with required and optional options', () => {
        it('uses the given core', () => {
          expect(command.core).to.equal(commandCore)
        })

        it('uses the given options', () => {
          expect(command.options).to.equal(commandOptions)
        })
      })
    })

    describe('#createErrors()', () => {
      it('creates errors of the given class for the given options', () => {
        expect(Command.createErrors(OptionError, ['one', 'two'])).to.deep.equal([new OptionError('one'), new OptionError('two')])
      })
    })

    describe('#difference()', () => {
      it('returns the difference of two Sets', () => {
        expect(Command.difference(new Set([1, 2]), new Set([2, 3]))).to.deep.equal(new Set([1]))
      })
    })

    describe('#getOptionNames()', () => {
      it('returns the names of the given options Object', () => {
        expect(Command.getOptionNames(commandOptions)).to.deep.equal(new Set(['first', 'second']))
      })
    })

    describe('#run()', () => {
      context('for a command without options', () => {
        context('given empty options', () => {
          it('returns a String result', () => {
            expect(simpleCommand.run({})).to.equal(greeting)
          })
        })

        context('given any option', () => {
          it('returns a String result', () => {
            expect(simpleCommand.run({ extra: true })).to.equal(greeting)
          })
        })
      })

      context('for a command with required and optional options', () => {
        context('given empty options', () => {
          it('throws a ValidationError', () => {
            expect(() => command.run({})).to.throw(ValidationError)
          })
        })

        context('given only the required option', () => {
          it('returns a String result', () => {
            expect(command.run({ first: 1 })).to.equal('1')
          })
        })

        context('given only the optional option', () => {
          it('throws a ValidationError', () => {
            expect(
              () => command.run({ second: 2 })
            ).to.throw(ValidationError)
          })
        })

        context('given both options', () => {
          it('returns a String result', () => {
            expect(command.run(options)).to.equal('3')
          })
        })

        context('given an invalid option', () => {
          it('throws a ValidationError', () => {
            expect(
              () => command.run({ first: 1, invalid: true })
            ).to.throw(ValidationError)
          })
        })
      })
    })

    describe('#validate()', () => {
      function expectValidationErrors (command, options, expected) {
        var errors = command.validate(new Set(options))
        expect(errors).to.deep.equal(new Set(expected))
      }

      context('for a command with no options', () => {
        context('given no options', () => {
          it('returns no errors', () => {
            expectValidationErrors(simpleCommand, [], [])
          })
        })

        context('given an option', () => {
          it('returns no errors', () => {
            expectValidationErrors(simpleCommand, ['extra'], [])
          })
        })
      })

      context('for a command with a required option and an optional option', () => {
        context('given no options', () => {
          it('returns a MissingOptionError', () => {
            expectValidationErrors(command, [], [
              new MissingOptionError('first')
            ])
          })
        })

        context('given the required option', () => {
          it('returns no errors', () => {
            expectValidationErrors(command, ['first'], [])
          })
        })

        context('given the optional option', () => {
          it('returns a MissingOptionError', () => {
            expectValidationErrors(command, ['second'], [
              new MissingOptionError('first')
            ])
          })
        })

        context('given an invalid option', () => {
          it('returns a MissingOptionError and an InvalidOptionError', () => {
            expectValidationErrors(command, ['invalid'], [
              new MissingOptionError('first'),
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the required option and the optional option', () => {
          it('returns no errors', () => {
            expectValidationErrors(command, ['first', 'second'], [])
          })
        })

        context('given the required option and an invalid option', () => {
          it('returns an InvalidOptionError', () => {
            expectValidationErrors(command, ['first', 'invalid'], [
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the optional option and an invalid option', () => {
          it('returns a MissingOptionError and an InvalidOptionError', () => {
            expectValidationErrors(command, ['second', 'invalid'], [
              new MissingOptionError('first'),
              new InvalidOptionError('invalid')
            ])
          })
        })

        context('given the required option, the optional option, and an invalid option', () => {
          it('returns an InvalidOptionError', () => {
            expectValidationErrors(command, ['first', 'second', 'invalid'], [
              new InvalidOptionError('invalid')
            ])
          })
        })
      })
    })
  })

  describe('Runner', () => {
    describe('constructor', () => {
      it('uses the given command', () => {
        expect(new Runner(command).command).to.equal(command)
      })
    })
  })

  describe('API', () => {
    var api = new API(command)

    var req = new http.IncomingMessage()
    req.url = 'http://localhost:3000?first=1&second=2'
    var parsedReq = url.parse(req.url)

    after(() => api.close())

    describe('constructor', () => {
      it('creates a server', () => {
        expect(api.server).to.be.an.instanceof(http.Server)
      })
    })

    describe('#callback()', () => {
      it('is a function', () => {
        expect(api.callback).to.be.an.instanceof(Function)
      })

      it('takes a request and a response', () => {
        expect(api.callback).to.have.length(2)
      })
    })

    describe('#server', () => {
      context('given valid options', () => {
        it('responds with a result', (done) => {
          request(api.server)
            .get(parsedReq.path)
            .expect(200, '3\n')
            .expect('content-type', 'text/plain')
            .end(done)
        })
      })

      context('given invalid options', () => {
        it('responds with validation errors', (done) => {
          request(api.server)
            .get('?invalid')
            .expect(403, {
              errors: [
                {
                  name: 'MissingOptionError',
                  option: 'first'
                },
                {
                  name: 'InvalidOptionError',
                  option: 'invalid'
                }
              ]
            })
            .expect('content-type', 'application/json')
            .end(done)
        })
      })
    })

    describe('#callback()', () => {
      it('is a function', () => {
        expect(api.callback).to.be.an.instanceof(Function)
      })

      it('takes a request and a response', () => {
        expect(api.callback.length).to.equal(2)
      })
    })

    describe('#close()', () => {
      it('closes the server', (done) => {
        api.close()

        request(parsedReq.host)
          .get(parsedReq.path)
          .end((err) => {
            err ? done() : done('Error: API server should be closed')
          })
      })
    })

    describe('.options()', () => {
      it('returns options from the given request', () => {
        expect(API.options(req)).to.deep.equal(stringOptions)
      })
    })

    describe('#run()', () => {
      it('runs its server for the given command', (done) => {
        api.run()

        request(parsedReq.host)
          .get(parsedReq.path)
          .end(done)
      })
    })
  })

  describe('CLI', () => {
    var args
    var cli = new CLI(command)

    before(() => { args = 'node cli.js --first 1 --second 2' })
    beforeEach(() => { process.argv = args.split(' ') })

    describe('#formatOptions()', () => {
      it('formats the given options for usage information', () => {
        expect(CLI.formatOptions(commandOptions)).to.equal('  --first              first number to add\n  --second             second number to add')
      })
    })

    describe('#help()', () => {
      it('returns formatted help text', () => {
        var helpTextPath = path.join(__dirname, 'help.txt')
        var helpText = fs.readFileSync(helpTextPath, 'utf-8').trim()
        expect(cli.help()).to.equal(helpText)
      })
    })

    describe('.options()', () => {
      it('returns options from argv', () => {
        expect(CLI.options()).to.deep.equal(options)
      })
    })

    describe('#run()', () => {
      beforeEach(() => { sandbox.spy(cli, 'help') })

      // Sets up stubs for tests that would produce console output. This can't
      // be in a beforeEach() hook because the console stubs would hide mocha's
      // command line results.
      function stubOutput (callback) {
        return () => {
          sandbox.stub(console, 'error')
          sandbox.stub(console, 'log')
          cli.run()
          callback()
        }
      }

      context('given valid options', () => {
        it('runs a cli for the given command that prints a result', stubOutput(function () {
          expect(cli.help).to.not.have.been.called
          expect(console.error).to.not.have.been.called
          expect(console.log).to.have.been.calledOnce
          expect(console.log).to.have.been.calledWithExactly('3')
        }))
      })

      function itDisplaysHelp () {
        it('displays help', stubOutput(function () {
          expect(cli.help).to.have.been.calledOnce
          expect(cli.help).to.have.been.calledWithExactly()
        }))
      }

      context('given invalid options', () => {
        before(() => { args = 'node cli.js --invalid' })

        it('displays validation errors and help', stubOutput(() => {
          var expected = 'Error: Missing required option "first"\n' +
                         'Error: Invalid option "invalid"'
          expect(console.error).to.have.been.calledOnce
          expect(console.error).to.have.been.calledWithExactly(expected)
          expect(console.log).to.have.been.called
        }))

        itDisplaysHelp()
      })

      context('given the help flag', () => {
        before(() => { args = 'node cli.js --help' })

        itDisplaysHelp()
      })
    })
  })
})
<|endoftext|>


<commit_before>
// centralized stdout writer.

exports.doColor = doColor
exports.write = write

var npm = require("../../npm.js")
  , stdio = process.binding("stdio")
  , streams = {}
  , ttys = {}
  , net = require("net")
  , util = require("util")
  , deadStreams = {}

function doColor (stream) {
  var conf = npm.config.get("color")
  return (!conf) ? false
       : (conf === "always") ? true
       : isatty(stream)
}
function isatty (stream) {
  // console.error("isatty?", stream)
  if (!stdio.isatty) return true
  if (!stream) return false
  if (stream.isTTY) return true
  if (stream && stream.fd) stream.isTTY = stdio.isatty(stream.fd)
  return stream.isTTY
}

function write (args, stream, lf, cb) {
  // console.error("write", [args, stream, lf, cb])
  if (typeof cb !== "function" && typeof lf === "function") {
    cb = lf
    lf = null
  }
  if (typeof cb !== "function" && typeof stream === "function") {
    cb = stream
    stream = npm.config.get("outfd")
  }

  stream = getStream(stream)
  // console.error("gotStream", stream)
  if (lf == null) lf = isatty(stream)
  if (!stream) return cb && cb(), false
  if (!Array.isArray(args)) args = [args]

  // console.error("write", args)

  var msg = ""
    , colored = doColor(stream)
  msg = args.map(function (arg) {
    if (typeof arg !== "string") {
      return util.inspect(arg, false, 5, colored) + "\n"
    }
    if (!colored) arg = arg.replace(/\033\[[0-9;]*m/g, '')
    if (!npm.config.get("unicode")) {
      arg = arg.replace(/└/g, "`")
               .replace(/─/g, "-")
               .replace(/├/g, "+")
               .replace(/┬/g, "-")
    }
    return arg
  }).join(" ")

  // listen to the "output" event to cancel/modify/redirect
  npm.output = {stream:stream, message:msg}
  npm.emit("output", npm.output)
  if (!npm.output) return cb && cb(), false // cancelled
  stream = npm.output.stream
  msg = npm.output.message

  // EPIPE errors just mean that the stream is not listening
  // any more.  Mark the stream as dead, and return.
  if (deadStreams[stream.fd]) {
    return cb && cb(), false
  }
  if (!deadStreams.hasOwnProperty(stream.fd)) {
    deadStreams[stream.fd] = false
    stream.on("error", function (er) {
      if (er.code === "EPIPE") {
        deadStreams[stream.fd] = true
        return cb && cb()
      }
      if (stream.listeners("error").length === 1) {
        throw er
      }
    })
  }

  // use the \r\n in case we're in raw mode.
  msg = msg.split(/\r?\n/).concat("").join(lf ? "\r\n" : "\n")
  // output to stderr should be synchronous
  if (stream === process.stderr || stream.fd === stdio.stderrFD) {
    stdio.writeError(msg)
    if (cb) cb()
    return true
  }
  // console.error("writing ", msg)
  var flushed = stream.write(msg)
  if (flushed && cb) {
    process.nextTick(cb)
  } else {
    stream.once("drain", cb)
  }
  return flushed
}

var hadError = false
function getStream (fd) {
  if (hadError) return

  var stream
  if (!fd && fd !== 0) return
  if (typeof fd === "string") fd = +fd

  // console.error("getStream", fd, hadError)

  if (fd && typeof fd === "object") {
    stream = fd
    fd = fd.fd
  } else if (streams[fd]) {
    stream = streams[fd]
  } else {
    // console.error("setting based on stdio")
    var stdio = process.binding("stdio")
    // console.error(stdio)
    switch (fd) {
      case stdio.stdoutFD:
        stream = process.stdout
        stream.fd = fd
        stream.writable = true
        // console.error("---stdoutFD", stream)
        break
      case stdio.stderrFD:
        stream = process.stderr
        stream.fd = fd
        stream.writable = true
        // console.error("---stderrFD", stream)
        break
      default:
        try {
          stream = new net.Stream(fd)
          if (!stream || !stream.writable) {
            throw new Error("Stream not writable")
          }
        } catch (ex) {
          // if this fails, then regular logging is most likely broken.
          var er = new Error("cannot output to fd "+fd + ": "+
                             (ex.stack || ex.message).substr(7) + "\n")
          console.error(er.stack)
          hadError = true
          process.exit(1)
        }
    }
  }

  if (!stream || !stream.writable) return
  return streams[fd] = stream
}
<commit_msg>Better fd detection<commit_after>
// centralized stdout writer.

exports.doColor = doColor
exports.write = write

var npm = require("../../npm.js")
  , stdio = process.binding("stdio")
  , streams = {}
  , ttys = {}
  , net = require("net")
  , util = require("util")
  , deadStreams = {}

function doColor (stream) {
  var conf = npm.config.get("color")
  return (!conf) ? false
       : (conf === "always") ? true
       : isatty(stream)
}
function isatty (stream) {
  // console.error("isatty?", stream)
  if (!stdio.isatty) return true
  if (!stream) return false
  if (stream.isTTY) return true
  if (stream && (typeof stream.fd === "number")) {
    stream.isTTY = stdio.isatty(stream.fd)
  }
  return stream.isTTY
}

function write (args, stream, lf, cb) {
  // console.error("write", [args, stream, lf, cb])
  if (typeof cb !== "function" && typeof lf === "function") {
    cb = lf
    lf = null
  }
  if (typeof cb !== "function" && typeof stream === "function") {
    cb = stream
    stream = npm.config.get("outfd")
  }

  stream = getStream(stream)
  // console.error("gotStream", stream)
  if (lf == null) lf = isatty(stream)
  if (!stream) return cb && cb(), false
  if (!Array.isArray(args)) args = [args]

  // console.error("write", args)

  var msg = ""
    , colored = doColor(stream)
  msg = args.map(function (arg) {
    if (typeof arg !== "string") {
      return util.inspect(arg, false, 5, colored) + "\n"
    }
    if (!colored) arg = arg.replace(/\033\[[0-9;]*m/g, '')
    if (!npm.config.get("unicode")) {
      arg = arg.replace(/└/g, "`")
               .replace(/─/g, "-")
               .replace(/├/g, "+")
               .replace(/┬/g, "-")
    }
    return arg
  }).join(" ")

  // listen to the "output" event to cancel/modify/redirect
  npm.output = {stream:stream, message:msg}
  npm.emit("output", npm.output)
  if (!npm.output) return cb && cb(), false // cancelled
  stream = npm.output.stream
  msg = npm.output.message

  // EPIPE errors just mean that the stream is not listening
  // any more.  Mark the stream as dead, and return.
  if (deadStreams[stream.fd]) {
    return cb && cb(), false
  }
  if (!deadStreams.hasOwnProperty(stream.fd)) {
    deadStreams[stream.fd] = false
    stream.on("error", function (er) {
      if (er.code === "EPIPE") {
        deadStreams[stream.fd] = true
        return cb && cb()
      }
      if (stream.listeners("error").length === 1) {
        throw er
      }
    })
  }

  // use the \r\n in case we're in raw mode.
  msg = msg.split(/\r?\n/).concat("").join(lf ? "\r\n" : "\n")
  // output to stderr should be synchronous
  if (stream === process.stderr || stream.fd === stdio.stderrFD) {
    stdio.writeError(msg)
    if (cb) cb()
    return true
  }
  // console.error("writing ", msg)
  var flushed = stream.write(msg)
  if (flushed && cb) {
    process.nextTick(cb)
  } else {
    stream.once("drain", cb)
  }
  return flushed
}

var hadError = false
function getStream (fd) {
  if (hadError) return

  var stream
  if (!fd && fd !== 0) return
  if (typeof fd === "string") fd = +fd

  // console.error("getStream", fd, hadError)

  if (fd && typeof fd === "object") {
    stream = fd
    fd = fd.fd
  } else if (streams[fd]) {
    stream = streams[fd]
  } else {
    // console.error("setting based on stdio")
    var stdio = process.binding("stdio")
    // console.error(stdio)
    switch (fd) {
      case stdio.stdoutFD:
        stream = process.stdout
        stream.fd = fd
        stream.writable = true
        // console.error("---stdoutFD", stream)
        break
      case stdio.stderrFD:
        stream = process.stderr
        stream.fd = fd
        stream.writable = true
        // console.error("---stderrFD", stream)
        break
      default:
        try {
          stream = new net.Stream(fd)
          if (!stream || !stream.writable) {
            throw new Error("Stream not writable")
          }
        } catch (ex) {
          // if this fails, then regular logging is most likely broken.
          var er = new Error("cannot output to fd "+fd + ": "+
                             (ex.stack || ex.message).substr(7) + "\n")
          console.error(er.stack)
          hadError = true
          process.exit(1)
        }
    }
  }

  if (!stream || !stream.writable) return
  return streams[fd] = stream
}
<|endoftext|>


<commit_before>import glob from 'glob'
import _ from 'lodash'
import fs from 'fs'
import select from 'unist-util-select'
import yaml from 'yaml-js'
import remarkAbstract from 'remark'
const remark = remarkAbstract({commonmark: true})
import remarkHtmlAbstract from 'remark-html'
const thing = {}
const toHTML = new remarkHtmlAbstract(thing)
import moment from 'moment'
import sanitizeHTML from 'sanitize-html'
import excerptHTML from 'excerpt-html'
import path from 'path'
import parseFilepath from 'parse-filepath'
import Promise from 'bluebird'

import createPath from './createPath'

import {
  graphql,
  GraphQLSchema,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLScalarType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLFloat,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
} from 'graphql';

import {
  nodeDefinitions,
  globalIdField,
  fromGlobalId
} from 'graphql-relay';

import {
  connectionFromArray
} from 'graphql-relay';

import {
  connectionArgs,
  connectionDefinitions
} from 'graphql-relay';

let markdown = []
const bootSchema = (directory) => {
  return new Promise((resolve, reject) => {
    console.log('directory to glob', `${directory}/pages/**/*.md`)
    let pages = []
    glob(`${directory}/pages/**/*.md`, (err, files) => {
      _.each(files, (file) => {
        const page = {}
        page.src = fs.readFileSync(file, 'utf-8')
        const ast = remark.parse(page.src)
        page.ast = ast
        page.bodyHTML = thing.Compiler.prototype.compile(page.ast)
        page.headings = select(ast, 'heading').map((heading) => {
          return {
            value: _.first(select(heading, 'text').map((text) => text.value)),
            depth: heading.depth,
          }
        })
        const parsedFrontmatter = _.first(select(ast, 'yaml').map((heading) => yaml.load(heading.value)))
        const relativeDirectory = parseFilepath(path.relative(`${directory}/pages/`, file)).dirname

        // Create path.
        let filePath
        if (_.includes(relativeDirectory, '---')) {
          // This is for my blog bricolage.io. This will be moved out soonish.
          filePath = `/${relativeDirectory.split('---')[1]}/`
        } else {
          filePath = createPath(path.join(directory, 'pages'), file)
        }
        // TODO put linkPrefix in gatsby.config.js and somehow get certain
        // context stuff into gatsby-helpers.js
        // TODO post issue for why manifest paths wrong
        // TODO post stackoverflow about how to get static values — ping Joe.
        // Prefix stuff — just store this as another key "prefixedPath"?
        pages.push({
          id: path.relative(directory, path.resolve(file)),
          path: filePath,
          ...page,
          frontmatter: {
            path: filePath,
            ...parsedFrontmatter,
          },
        })
      })
      pages = _.sortBy(pages, (page) => page.frontmatter.date).reverse()
      console.log('finishing loading files')
      return resolve(pages)
    })
  })
}

const main = (directory) => {
  let config = require(`${directory}/gatsby.config`)
  if (config.default) {
    config = config.default
  }

  return new Promise((resolve, reject) => {
    bootSchema(directory)
    .catch((error) => console.log('error in booting schema', error))
    .then((pages) => {
      /**
       * Using our shorthand to describe type systems, the type system for our
       * example will be the followng:
       *
       * interface Node {
       *   id: ID!
       * }
       */

      /**
       * We get the node interface and field from the relay library.
       *
       * The first method is the way we resolve an ID to its object. The second is the
       * way we resolve an object that implements node to its type.
       */
      const {nodeInterface, nodeField} = nodeDefinitions(
        (globalId) => {
          const {type, id} = fromGlobalId(globalId);
          if (type === 'Faction') {
            return getFaction(id);
          } else if (type === 'Ship') {
            return getShip(id);
          } else {
            return null;
          }
        },
        (obj) => {
          return obj.ships ? factionType : shipType;
        }
      );

      /**
       * We define our basic ship type.
       *
       * This implements the following type system shorthand:
       *   type Ship : Node {
       *     id: String!
       *     name: String
       *   }
       */
      var shipType = new GraphQLObjectType({
        name: 'Ship',
        description: 'A ship in the Star Wars saga',
        fields: () => ({
          id: globalIdField(),
          name: {
            type: GraphQLString,
            description: 'The name of the ship.',
          },
        }),
        interfaces: [nodeInterface]
      });

      /**
       * We define a connection between a faction and its ships.
       *
       * connectionType implements the following type system shorthand:
       *   type ShipConnection {
       *     edges: [ShipEdge]
       *     pageInfo: PageInfo!
       *   }
       *
       * connectionType has an edges field - a list of edgeTypes that implement the
       * following type system shorthand:
       *   type ShipEdge {
       *     cursor: String!
       *     node: Ship
       *   }
       */
      var {connectionType: shipConnection} =
        connectionDefinitions({nodeType: shipType});

      /**
       * We define our faction type, which implements the node interface.
       *
       * This implements the following type system shorthand:
       *   type Faction : Node {
       *     id: String!
       *     name: String
       *     ships: ShipConnection
       *   }
       */
      var factionType = new GraphQLObjectType({
        name: 'Faction',
        description: 'A faction in the Star Wars saga',
        fields: () => ({
          id: globalIdField(),
          name: {
            type: GraphQLString,
            description: 'The name of the faction.',
          },
          ships: {
            type: shipConnection,
            description: 'The ships used by the faction.',
            args: connectionArgs,
            resolve: (faction, args) => connectionFromArray(
              faction.ships.map((id) => getShip(id)),
              args
            ),
          }
        }),
        interfaces: [nodeInterface]
      });

      const HeadingType = new GraphQLObjectType({
        name: 'Heading',
        fields: {
          value: {
            type: GraphQLString,
            resolve(heading) {
              return heading.value
            }
          },
          depth: {
            type: GraphQLInt,
            resolve(heading) {
              return heading.depth
            }
          },
        },
      })

      const getGraphQLType = (key, value) => {
        if (Array.isArray(value)) {
          const headType = getGraphQLType('', value[0]);
          return new GraphQLList(headType)
        }

        if (value === null) {
          return null
        }

        // Check if date object.
        if (typeof value.getMonth === 'function') {
          return 'DATE'
        }

        switch(typeof value) {
          case 'boolean':
            return GraphQLBoolean
          case 'string':
            return GraphQLString
          case 'object':
            return GraphQLObjectType
          case 'number':
            return value % 1 == 0
              ? GraphQLInt
              : GraphQLFloat
        }
        return null
      }

      const metadataFields = () => {
        let fields = {}
        // TODO make this work with sub-objects.
        console.log(config)
        _.each(config.siteMetadata, (v, k) => {
          const type = getGraphQLType(k, v)
          if (type === 'DATE') {
            fields[k] = {
              type: GraphQLString,
              args: {
                formatString: {
                  type: GraphQLString,
                },
              },
              resolve(date, { formatString }) {
                if (formatString) {
                  return moment(date).format(formatString)
                } else {
                  return date
                }
              }
            }
          } else {
            fields[k] = { type: getGraphQLType(k, v) }
          }
        })
        console.log(fields)

        return fields
      }

      const frontmatterFields = () => {
        // Create object with the first example of every frontmatter key.
        let frontmatterFieldExamples = {}
        _.each(pages, (page) => {
          _.each(page.frontmatter, (v, k) => {
            if (!frontmatterFieldExamples[k]) {
              frontmatterFieldExamples[k] = v
            }
          })
        })

        let fields = {}
        // TODO make this work with sub-objects.
        _.each(frontmatterFieldExamples, (v, k) => {
          const type = getGraphQLType(k, v)
          if (type === 'DATE') {
            fields[k] = {
              type: GraphQLString,
              args: {
                formatString: {
                  type: GraphQLString,
                },
              },
              resolve({ date }, { formatString }) {
                if (formatString) {
                  return moment(date).format(formatString)
                } else {
                  return date
                }
              }
            }
          } else {
            fields[k] = { type: getGraphQLType(k, v) }
          }
        })

        if (fields.readNext) {
          fields.readNext = {
            type: markdownType,
            resolve(frontmatter, args, context) {
              return _.find(pages, (page) => page.path === frontmatter.readNext)
            }
          }
        }

        return fields
      }

      const markdownType = new GraphQLObjectType({
        name: 'Markdown',
        fields: () => ({
          id: {
            type: new GraphQLNonNull(GraphQLID),
          },
          path: {
            type: GraphQLString,
          },
          frontmatter: {
            type: new GraphQLObjectType({
              name: 'Frontmatter',
              fields: frontmatterFields(),
            }),
          },
          headings: {
            type: new GraphQLList(HeadingType),
            resolve(page) {
              return page.headings
            }
          },
          bodyMarkdown: {
            type: GraphQLString,
            resolve(page) {
              return page.src
            }
          },
          bodyHTML: {
            type: GraphQLString,
            resolve(page) {
              return page.bodyHTML
            }
          },
          timeToRead: {
            type: GraphQLInt,
            resolve(page) {
              let timeToRead = 0
              const pureText = sanitizeHTML(page.bodyHTML, { allowTags: [] })
              const avgWPM = 265
              const wordCount = _.words(pureText).length
              timeToRead = Math.round(wordCount / avgWPM)
              if (timeToRead === 0) { timeToRead = 1 }
              return timeToRead
            }
          },
          excerpt: {
            type: GraphQLString,
            args: {
              pruneLength: {
                type: GraphQLInt,
                defaultValue: 140,
              },
            },
            resolve(page, { pruneLength }) {
              return excerptHTML(page.bodyHTML, {
                pruneLength,
              })
            },
          },
          readNext: {
            type: GraphQLString,
            resolve(page) {
              return page.readNext
            }
          },
        }),
        interfaces: [nodeInterface],
      })

      const {connectionType: markdownConnection} =
        connectionDefinitions(
          {
            nodeType: markdownType,
            connectionFields: () => ({
              totalCount: {
                type: GraphQLInt,
              },
            }),
          }
        )

      const configType = new GraphQLObjectType({
        name: 'Config',
        fields: {
          siteMetadata: {
            type: new GraphQLObjectType({
              name: 'SiteMetadata',
              fields: metadataFields(),
            }),
          },
          development: {
            type: new GraphQLObjectType({
              name: 'DevelopmentConfig',
              fields: {
                port: { type: GraphQLString },
              },
            }),
          },
        },
      })

      const schema = new GraphQLSchema({
        query: new GraphQLObjectType({
          name: 'RootQueryType',
          fields: {
            allMarkdown: {
              type: markdownConnection,
              description: 'All markdown files',
              args: {
                ...connectionArgs,
                tag: {
                  type: GraphQLString,
                  description: 'Filter by posts that have this tag',
                },
              },
              resolve (markdown, args) {
                let filteredPages
                if (args.tag) {
                  filteredPages = _.filter(pages, (page) => {
                    if (!page.frontmatter.tags) {
                      return false
                    } else {
                      return _.includes(
                        page.frontmatter.tags.map(
                          (tag) => tag.toLowerCase()
                        ), args.tag.toLowerCase()
                      )
                    }
                  })
                } else {
                  filteredPages = _.clone(pages)
                }
                const result = connectionFromArray(
                  filteredPages,
                  args
                )
                result.totalCount = filteredPages.length
                return result
              }
            },
            markdown: {
              type: markdownType,
              args: {
                path: {
                  type: new GraphQLNonNull(GraphQLString),
                  description: 'URL path to markdown page',
                },
              },
              resolve (root, args) {
                return _.find(pages, (page) => page.path === args.path)
              },
            },
            config: {
              type: configType,
              resolve (root, args) {
                return config
              },
            },
          },
        })
      });

      resolve(schema)
    })
  })
}

module.exports = main
<commit_msg>Change config to site in default GraphQL schema + add page type<commit_after>import glob from 'glob'
import _ from 'lodash'
import fs from 'fs'
import select from 'unist-util-select'
import yaml from 'yaml-js'
import remarkAbstract from 'remark'
const remark = remarkAbstract({commonmark: true})
import remarkHtmlAbstract from 'remark-html'
const thing = {}
const toHTML = new remarkHtmlAbstract(thing)
import moment from 'moment'
import sanitizeHTML from 'sanitize-html'
import excerptHTML from 'excerpt-html'
import path from 'path'
import parseFilepath from 'parse-filepath'
import Promise from 'bluebird'

import createPath from './createPath'

import {
  graphql,
  GraphQLSchema,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLScalarType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLFloat,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
} from 'graphql';

import {
  nodeDefinitions,
  globalIdField,
  fromGlobalId
} from 'graphql-relay';

import {
  connectionFromArray
} from 'graphql-relay';

import {
  connectionArgs,
  connectionDefinitions
} from 'graphql-relay';

let markdown = []
const bootSchema = (directory) => {
  return new Promise((resolve, reject) => {
    console.log('directory to glob', `${directory}/pages/**/*.md`)
    let pages = []
    glob(`${directory}/pages/**/*.md`, (err, files) => {
      _.each(files, (file) => {
        const page = {}
        page.src = fs.readFileSync(file, 'utf-8')
        const ast = remark.parse(page.src)
        page.ast = ast
        page.bodyHTML = thing.Compiler.prototype.compile(page.ast)
        page.headings = select(ast, 'heading').map((heading) => {
          return {
            value: _.first(select(heading, 'text').map((text) => text.value)),
            depth: heading.depth,
          }
        })
        const parsedFrontmatter = _.first(select(ast, 'yaml').map((heading) => yaml.load(heading.value)))
        const relativeDirectory = parseFilepath(path.relative(`${directory}/pages/`, file)).dirname

        // Create path.
        let filePath
        if (_.includes(relativeDirectory, '---')) {
          // This is for my blog bricolage.io. This will be moved out soonish.
          filePath = `/${relativeDirectory.split('---')[1]}/`
        } else {
          filePath = createPath(path.join(directory, 'pages'), file)
        }
        // TODO put linkPrefix in gatsby.config.js and somehow get certain
        // context stuff into gatsby-helpers.js
        // TODO post issue for why manifest paths wrong
        // TODO post stackoverflow about how to get static values — ping Joe.
        // Prefix stuff — just store this as another key "prefixedPath"?
        pages.push({
          id: path.relative(directory, path.resolve(file)),
          path: filePath,
          ...page,
          frontmatter: {
            path: filePath,
            ...parsedFrontmatter,
          },
        })
      })
      pages = _.sortBy(pages, (page) => page.frontmatter.date).reverse()
      console.log('finishing loading files')
      return resolve(pages)
    })
  })
}

const main = (directory) => {
  let config = require(`${directory}/gatsby.config`)
  if (config.default) {
    config = config.default
  }

  return new Promise((resolve, reject) => {
    bootSchema(directory)
    .catch((error) => console.log('error in booting schema', error))
    .then((pages) => {
      /**
       * Using our shorthand to describe type systems, the type system for our
       * example will be the followng:
       *
       * interface Node {
       *   id: ID!
       * }
       */

      /**
       * We get the node interface and field from the relay library.
       *
       * The first method is the way we resolve an ID to its object. The second is the
       * way we resolve an object that implements node to its type.
       */
      const {nodeInterface, nodeField} = nodeDefinitions(
        (globalId) => {
          const {type, id} = fromGlobalId(globalId);
          if (type === 'Faction') {
            return getFaction(id);
          } else if (type === 'Ship') {
            return getShip(id);
          } else {
            return null;
          }
        },
        (obj) => {
          return obj.ships ? factionType : shipType;
        }
      );

      /**
       * We define our basic ship type.
       *
       * This implements the following type system shorthand:
       *   type Ship : Node {
       *     id: String!
       *     name: String
       *   }
       */
      var shipType = new GraphQLObjectType({
        name: 'Ship',
        description: 'A ship in the Star Wars saga',
        fields: () => ({
          id: globalIdField(),
          name: {
            type: GraphQLString,
            description: 'The name of the ship.',
          },
        }),
        interfaces: [nodeInterface]
      });

      /**
       * We define a connection between a faction and its ships.
       *
       * connectionType implements the following type system shorthand:
       *   type ShipConnection {
       *     edges: [ShipEdge]
       *     pageInfo: PageInfo!
       *   }
       *
       * connectionType has an edges field - a list of edgeTypes that implement the
       * following type system shorthand:
       *   type ShipEdge {
       *     cursor: String!
       *     node: Ship
       *   }
       */
      var {connectionType: shipConnection} =
        connectionDefinitions({nodeType: shipType});

      /**
       * We define our faction type, which implements the node interface.
       *
       * This implements the following type system shorthand:
       *   type Faction : Node {
       *     id: String!
       *     name: String
       *     ships: ShipConnection
       *   }
       */
      var factionType = new GraphQLObjectType({
        name: 'Faction',
        description: 'A faction in the Star Wars saga',
        fields: () => ({
          id: globalIdField(),
          name: {
            type: GraphQLString,
            description: 'The name of the faction.',
          },
          ships: {
            type: shipConnection,
            description: 'The ships used by the faction.',
            args: connectionArgs,
            resolve: (faction, args) => connectionFromArray(
              faction.ships.map((id) => getShip(id)),
              args
            ),
          }
        }),
        interfaces: [nodeInterface]
      });

      const HeadingType = new GraphQLObjectType({
        name: 'Heading',
        fields: {
          value: {
            type: GraphQLString,
            resolve(heading) {
              return heading.value
            }
          },
          depth: {
            type: GraphQLInt,
            resolve(heading) {
              return heading.depth
            }
          },
        },
      })

      const getGraphQLType = (key, value) => {
        if (Array.isArray(value)) {
          const headType = getGraphQLType('', value[0]);
          return new GraphQLList(headType)
        }

        if (value === null) {
          return null
        }

        // Check if date object.
        if (typeof value.getMonth === 'function') {
          return 'DATE'
        }

        switch(typeof value) {
          case 'boolean':
            return GraphQLBoolean
          case 'string':
            return GraphQLString
          case 'object':
            return GraphQLObjectType
          case 'number':
            return value % 1 == 0
              ? GraphQLInt
              : GraphQLFloat
        }
        return null
      }

      const metadataFields = () => {
        let fields = {}
        // TODO make this work with sub-objects.
        console.log(config)
        _.each(config.siteMetadata, (v, k) => {
          const type = getGraphQLType(k, v)
          if (type === 'DATE') {
            fields[k] = {
              type: GraphQLString,
              args: {
                formatString: {
                  type: GraphQLString,
                },
              },
              resolve(date, { formatString }) {
                if (formatString) {
                  return moment(date).format(formatString)
                } else {
                  return date
                }
              }
            }
          } else {
            fields[k] = { type: getGraphQLType(k, v) }
          }
        })
        console.log(fields)

        return fields
      }

      const frontmatterFields = () => {
        // Create object with the first example of every frontmatter key.
        let frontmatterFieldExamples = {}
        _.each(pages, (page) => {
          _.each(page.frontmatter, (v, k) => {
            if (!frontmatterFieldExamples[k]) {
              frontmatterFieldExamples[k] = v
            }
          })
        })

        let fields = {}
        // TODO make this work with sub-objects.
        _.each(frontmatterFieldExamples, (v, k) => {
          const type = getGraphQLType(k, v)
          if (type === 'DATE') {
            fields[k] = {
              type: GraphQLString,
              args: {
                formatString: {
                  type: GraphQLString,
                },
              },
              resolve({ date }, { formatString }) {
                if (formatString) {
                  return moment(date).format(formatString)
                } else {
                  return date
                }
              }
            }
          } else {
            fields[k] = { type: getGraphQLType(k, v) }
          }
        })

        if (fields.readNext) {
          fields.readNext = {
            type: markdownType,
            resolve(frontmatter, args, context) {
              return _.find(pages, (page) => page.path === frontmatter.readNext)
            }
          }
        }

        return fields
      }

      const markdownType = new GraphQLObjectType({
        name: 'Markdown',
        fields: () => ({
          id: {
            type: new GraphQLNonNull(GraphQLID),
          },
          path: {
            type: GraphQLString,
          },
          frontmatter: {
            type: new GraphQLObjectType({
              name: 'Frontmatter',
              fields: frontmatterFields(),
            }),
          },
          headings: {
            type: new GraphQLList(HeadingType),
            resolve(page) {
              return page.headings
            }
          },
          bodyMarkdown: {
            type: GraphQLString,
            resolve(page) {
              return page.src
            }
          },
          bodyHTML: {
            type: GraphQLString,
            resolve(page) {
              return page.bodyHTML
            }
          },
          timeToRead: {
            type: GraphQLInt,
            resolve(page) {
              let timeToRead = 0
              const pureText = sanitizeHTML(page.bodyHTML, { allowTags: [] })
              const avgWPM = 265
              const wordCount = _.words(pureText).length
              timeToRead = Math.round(wordCount / avgWPM)
              if (timeToRead === 0) { timeToRead = 1 }
              return timeToRead
            }
          },
          excerpt: {
            type: GraphQLString,
            args: {
              pruneLength: {
                type: GraphQLInt,
                defaultValue: 140,
              },
            },
            resolve(page, { pruneLength }) {
              return excerptHTML(page.bodyHTML, {
                pruneLength,
              })
            },
          },
          readNext: {
            type: GraphQLString,
            resolve(page) {
              return page.readNext
            }
          },
        }),
        interfaces: [nodeInterface],
      })

      const pageType = new GraphQLObjectType({
        name: 'Page',
        fields: () => ({
          id: {
            type: new GraphQLNonNull(GraphQLID),
          },
          path: {
            type: GraphQLString,
          },
          component: {
            type: GraphQLString,
          },
        }),
        interfaces: [nodeInterface],
      })

      const {connectionType: markdownConnection} =
        connectionDefinitions(
          {
            nodeType: markdownType,
            connectionFields: () => ({
              totalCount: {
                type: GraphQLInt,
              },
            }),
          }
        )

      const {connectionType: pagesConnection} =
        connectionDefinitions(
          {
            nodeType: pageType,
            connectionFields: () => ({
              totalCount: {
                type: GraphQLInt,
              },
            }),
          }
        )

      const siteType = new GraphQLObjectType({
        name: 'Site',
        fields: {
          siteMetadata: {
            type: new GraphQLObjectType({
              name: 'SiteMetadata',
              fields: metadataFields(),
            }),
          },
          development: {
            type: new GraphQLObjectType({
              name: 'DevelopmentConfig',
              fields: {
                port: { type: GraphQLString },
              },
            }),
          },
          linkPrefix: {
            type: GraphQLString,
            name: 'prefixLink',
            description: 'Optionally prefix site links with this',
          },
          time: {
            type: GraphQLString,
            name: 'time',
            description: 'The current time (when you run the `gatsby` command).',
            resolve: () => new Date().toJSON(),
          },
          allPages: {
            type: pagesConnection,
            description: 'All page configuration objects',
            args: {
              ...connectionArgs,
            },
            resolve (site, args) {
              let pages = {...routesDB.data}
              pages = _.values(pages)
              //console.log(routesDB)
              //console.log(pages)
              pages = pages.map((page) => { page.id = page.path; return page })
              const result = connectionFromArray(
                routesDB.data,
                args
              )
              result.totalCount = routesDB.data.length
              return result
            }
          }
        },
      })

      const schema = new GraphQLSchema({
        query: new GraphQLObjectType({
          name: 'RootQueryType',
          fields: {
            allMarkdown: {
              type: markdownConnection,
              description: 'All markdown files',
              args: {
                ...connectionArgs,
                tag: {
                  type: GraphQLString,
                  description: 'Filter by posts that have this tag',
                },
              },
              resolve (markdown, args) {
                let filteredPages
                if (args.tag) {
                  filteredPages = _.filter(pages, (page) => {
                    if (!page.frontmatter.tags) {
                      return false
                    } else {
                      return _.includes(
                        page.frontmatter.tags.map(
                          (tag) => tag.toLowerCase()
                        ), args.tag.toLowerCase()
                      )
                    }
                  })
                } else {
                  filteredPages = _.clone(pages)
                }
                const result = connectionFromArray(
                  filteredPages,
                  args
                )
                result.totalCount = filteredPages.length
                return result
              }
            },
            markdown: {
              type: markdownType,
              args: {
                path: {
                  type: new GraphQLNonNull(GraphQLString),
                  description: 'URL path to markdown page',
                },
              },
              resolve (root, args) {
                return _.find(pages, (page) => page.path === args.path)
              },
            },
            site: {
              type: siteType,
              resolve (root, args) {
                return config
              },
            },
          },
        })
      });

      resolve(schema)
    })
  })
}

module.exports = main
<|endoftext|>


<commit_before>var cluster = require('cluster'),
	fs = require('fs'),
	gm = require('gm'),
	http = require('http'),
	path = require('path'),
	should = require('should');

var immpPath = path.join(process.cwd(), '/.tmp/immp'),
	gmOptions = {
		bufferStream: true,
		imageMagick: true
	};

describe('immp', function() {

	before(function(_done) {
		cluster.setupMaster({
			exec: path.join(process.cwd(), '/bin/www')
		});
		cluster.fork();
		setTimeout(function() {
			_done();
		}, 300);
	});

	after(function() {
		var immpFiles = fs.readdirSync(immpPath);
		immpFiles.forEach(function(_file) {
			fs.unlinkSync(path.join(immpPath, '/', _file));
		});
	});

	it('should resize by width', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=200x0', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(200);

					_done();
				});

		});

	});

	it('should resize by height', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=0x200', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.height.should.equal(200);

					_done();
				});

		});

	});

	it('should not upscale by default', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=2000x2000', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1920);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should upscale when upscaling is true', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=2000x2000&upscale=true', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(2000);
					_size.height.should.equal(1125);

					_done();
				});

		});

	});

	it('should resize by width and height and retain the aspect ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=100x200', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(100);
					_size.height.should.equal(56);

					_done();
				});

		}).on('error', function(_error) {
			_done(_error);
		});

	});

	it('should crop to a square', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=1x1', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1080);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop to a short width big height ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=9x16', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(608);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop to a big width short height ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=16x9', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1920);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop and resize', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=1x1&resize=50x100', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(50);
					_size.height.should.equal(50);

					_done();
				});

		});

	});

});<commit_msg>refactored some test vars<commit_after>var async = require('async'),
	cluster = require('cluster'),
	cwd = process.cwd(),
	fs = require('fs'),
	gm = require('gm'),
	http = require('http'),
	path = require('path'),
	should = require('should');

var immpPath = path.join(cwd, '/.tmp/immp'),
	gmOptions = {
		bufferStream: true,
		imageMagick: true
	};

describe('immp', function() {

	before(function(_done) {
		cluster.setupMaster({
			exec: path.join(cwd, '/bin/www')
		});
		cluster.fork();
		setTimeout(function() {
			_done();
		}, 300);
	});

	after(function() {
		var immpFiles = fs.readdirSync(immpPath);
		immpFiles.forEach(function(_file) {
			fs.unlinkSync(path.join(immpPath, '/', _file));
		});
	});

	it('should resize by width', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=200x0', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(200);

					_done();
				});

		});

	});

	it('should resize by height', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=0x200', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.height.should.equal(200);

					_done();
				});

		});

	});

	it('should not upscale by default', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=2000x2000', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1920);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should upscale when upscaling is true', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=2000x2000&upscale=true', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(2000);
					_size.height.should.equal(1125);

					_done();
				});

		});

	});

	it('should resize by width and height and retain the aspect ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&resize=100x200', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(100);
					_size.height.should.equal(56);

					_done();
				});

		}).on('error', function(_error) {
			_done(_error);
		});

	});

	it('should crop to a square', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=1x1', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1080);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop to a short width big height ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=9x16', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(608);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop to a big width short height ratio', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=16x9', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(1920);
					_size.height.should.equal(1080);

					_done();
				});

		});

	});

	it('should crop and resize', function(_done) {
		this.slow(5000);
		this.timeout(10000);

		http.get('http://localhost:3000/im/?image=/images/robot.jpg&crop=1x1&resize=50x100', function(_httpResponse) {

			gm(_httpResponse)
				.options(gmOptions)
				.size(function(_error, _size) {
					if (_error) return _done(_error);

					_size.width.should.equal(50);
					_size.height.should.equal(50);

					_done();
				});

		});

	});

});<|endoftext|>