<commit_before>/* -*- Mode: Javascript; js-indent-level: 2 -*- */

var testDatabase = require('./util/database');
var db = require('../')(testDatabase.url);
var uniqn = require('./util/ponies').uniqn;

var assert = require('assert');
var async = require('async');

describe('seraph#query, seraph#queryRaw', function() {
  it('should perform a cypher query', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return type(r), n.name?, n.age? ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{
          'type(r)': 'knows',
          'n.name?': 'Katie',
          'n.age?': 29
        }, {
          'type(r)': 'knows',
          'n.name?': 'Neil',
          'n.age?': 60
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should handle arrays in format string', function(done) {
    function createObjs(done) {
      db.save([{tool: 'glue'},
               {tool: 'laser'},
               {tool: 'toilet paper'}],
              function(err, tools) {
                var toolIds = tools.map(function(t) { return t.id; });
                done(null, toolIds.slice(0, 2), toolIds[2])
              });
    }

    function linkObjs(tools, tp, done) {
      db.relate(tools, 'ruins', tp, function(err, links) {
        done(null, tools);
      });
    }

    function query(tools, done) {
      var cypher = "start x = node(" + tools + ") ";
      cypher    += "match x -[:ruins]-> y ";
      cypher    += "return x.tool, y.tool ";
      cypher    += "order by x.tool";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{
          'x.tool': 'glue',
          'y.tool': 'toilet paper',
        }, {
          'x.tool': 'laser',
          'y.tool': 'toilet paper',
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should recurse into collect() arrays on query', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}],
              function(err, users) {
                done(null, users[0], users.slice(1));
              });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return x, collect(n)";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        delete result[0]['x'].id;
        delete result[0]['collect(n)'][0].id;
        delete result[0]['collect(n)'][1].id;
        // "sort" collect list
        if (result[0]['collect(n)'][0].name === "Katie") {
          var t = result[0]['collect(n)'][0];
          result[0]['collect(n)'][0] = result[0]['collect(n)'][1];
          result[0]['collect(n)'][1] = t;
        }
        assert.deepEqual([{
          'x': { name: 'Jon', age: 23 },
          'collect(n)': [{ name: 'Neil', age: 60 },
                         { name: 'Katie', age: 29 }]
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query and return whole objects', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1, users);
      });
    }

    function query(user, users, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return n ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([ users[1], users[0] ], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query and return rels', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1, users, links);
      });
    }

    function query(user, users, links, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return r ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([
          { start: user.id,
            end: users[1].id,
            type: 'knows',
            properties: {},
            id: links[1].id },
          { start: user.id,
            end: users[0].id,
            type: 'knows',
            properties: {},
            id: links[0].id }
        ], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query w/o parsing the result', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function queryRaw(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return type(r), n.name?, n.age? ";
      cypher    += "order by n.name";
      db.queryRaw(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual({
          data: [['knows', 'Katie', 29], ['knows', 'Neil', 60]],
          columns: ['type(r)', 'n.name?', 'n.age?']
        }, result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, queryRaw], done);
  });

  it('should handle optional entities', function(done) {
    function createObjs(done) {
      db.save({name: 'Jon', age: 23}, function(err, user) {
        done(null, user);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[?]-> n ";
      cypher    += "return x, n ";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{x: user, n: null}], result);
        done();
      });
    }

    async.waterfall([createObjs, query], done);
  });
});

describe('seraph#find', function() {
  it('should find some items based on a predicate', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: 'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        done();
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      db.find(predicate, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 3);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') >= 0);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf('Belinda') === -1);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  })

  it('should find some items based on an OR predicate', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: uniqueKey+'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        done();
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      predicate['name'] = uniqueKey+'Belinda';

      db.find(predicate, true, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 4);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') >= 0);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf(uniqueKey+'Belinda') >= 0);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  })

  it('should find some items based on a predicate with a custom starting point', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: 'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        users = users.slice(1);
        db.index(uniqueKey, users, 'some_thing', 'perhaps', function() {
          done();
        })
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      var startPoint = 'node:' + uniqueKey + '(some_thing="perhaps")';
      db.find(predicate, false, startPoint, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 2);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') === -1);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf('Belinda') === -1);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  });
});
<commit_msg>Test properly aligned count columns<commit_after>/* -*- Mode: Javascript; js-indent-level: 2 -*- */

var testDatabase = require('./util/database');
var db = require('../')(testDatabase.url);
var uniqn = require('./util/ponies').uniqn;

var assert = require('assert');
var async = require('async');

describe('seraph#query, seraph#queryRaw', function() {
  it('should perform a cypher query', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return type(r), n.name?, n.age? ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{
          'type(r)': 'knows',
          'n.name?': 'Katie',
          'n.age?': 29
        }, {
          'type(r)': 'knows',
          'n.name?': 'Neil',
          'n.age?': 60
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should handle arrays in format string', function(done) {
    function createObjs(done) {
      db.save([{tool: 'glue'},
               {tool: 'laser'},
               {tool: 'toilet paper'}],
              function(err, tools) {
                var toolIds = tools.map(function(t) { return t.id; });
                done(null, toolIds.slice(0, 2), toolIds[2])
              });
    }

    function linkObjs(tools, tp, done) {
      db.relate(tools, 'ruins', tp, function(err, links) {
        done(null, tools);
      });
    }

    function query(tools, done) {
      var cypher = "start x = node(" + tools + ") ";
      cypher    += "match x -[:ruins]-> y ";
      cypher    += "return x.tool, y.tool ";
      cypher    += "order by x.tool";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{
          'x.tool': 'glue',
          'y.tool': 'toilet paper',
        }, {
          'x.tool': 'laser',
          'y.tool': 'toilet paper',
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should recurse into collect() arrays on query', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}],
              function(err, users) {
                done(null, users[0], users.slice(1));
              });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return x, collect(n)";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        delete result[0]['x'].id;
        delete result[0]['collect(n)'][0].id;
        delete result[0]['collect(n)'][1].id;
        // "sort" collect list
        if (result[0]['collect(n)'][0].name === "Katie") {
          var t = result[0]['collect(n)'][0];
          result[0]['collect(n)'][0] = result[0]['collect(n)'][1];
          result[0]['collect(n)'][1] = t;
        }
        assert.deepEqual([{
          'x': { name: 'Jon', age: 23 },
          'collect(n)': [{ name: 'Neil', age: 60 },
                         { name: 'Katie', age: 29 }]
        }], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query and return whole objects', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1, users);
      });
    }

    function query(user, users, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return n ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([ users[1], users[0] ], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query and return rels', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1, users, links);
      });
    }

    function query(user, users, links, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return r ";
      cypher    += "order by n.name";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([
          { start: user.id,
            end: users[1].id,
            type: 'knows',
            properties: {},
            id: links[1].id },
          { start: user.id,
            end: users[0].id,
            type: 'knows',
            properties: {},
            id: links[0].id }
        ], result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, query], done);
  });

  it('should perform a cypher query w/o parsing the result', function(done) {
    function createObjs(done) {
      db.save([{name: 'Jon', age: 23},
               {name: 'Neil', age: 60},
               {name: 'Katie', age: 29}], function(err, users) {
        done(null, users[0], users.slice(1));
      });
    }

    function linkObjs(user1, users, done) {
      db.relate(user1, 'knows', users, function(err, links) {
        done(null, user1);
      });
    }

    function queryRaw(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[r]-> n ";
      cypher    += "return type(r), n.name?, n.age? ";
      cypher    += "order by n.name";
      db.queryRaw(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual({
          data: [['knows', 'Katie', 29], ['knows', 'Neil', 60]],
          columns: ['type(r)', 'n.name?', 'n.age?']
        }, result);
        done();
      });
    }

    async.waterfall([createObjs, linkObjs, queryRaw], done);
  });

  it('should handle optional entities', function(done) {
    function createObjs(done) {
      db.save({name: 'Jon', age: 23}, function(err, user) {
        done(null, user);
      });
    }

    function query(user, done) {
      var cypher = "start x = node(" + user.id + ") ";
      cypher    += "match x -[?]-> n ";
      cypher    += "return x, n ";
      db.query(cypher, function(err, result) {
        assert.ok(!err);
        assert.deepEqual([{x: user, n: null}], result);
        done();
      });
    }

    async.waterfall([createObjs, query], done);
  });
});

describe('seraph#find', function() {
  it('should find some items based on a predicate', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: 'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        done();
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      db.find(predicate, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 3);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') >= 0);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf('Belinda') === -1);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  })

  it('should find some items based on an OR predicate', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: uniqueKey+'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        done();
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      predicate['name'] = uniqueKey+'Belinda';

      db.find(predicate, true, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 4);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') >= 0);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf(uniqueKey+'Belinda') >= 0);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  })

  it('should find some items based on a predicate with a custom starting point', function(done) {
    var uniqueKey = 'seraph_find_test' + uniqn();
    function createObjs(done) {
      var objs = [ {name: 'Jon', age: 23},
                   {name: 'Neil', age: 60},
                   {name: 'Katie', age: 29} ];
      for (var index in objs) {
        objs[index][uniqueKey] = true;
      }
      objs[3] = {name: 'Belinda', age: 26};
      objs[3][uniqueKey] = false;

      db.save(objs, function(err, users) {
        users = users.slice(1);
        db.index(uniqueKey, users, 'some_thing', 'perhaps', function() {
          done();
        })
      });
    }

    function findObjs(done) {
      var predicate = {};
      predicate[uniqueKey] = true;
      var startPoint = 'node:' + uniqueKey + '(some_thing="perhaps")';
      db.find(predicate, false, startPoint, function(err, objs) {
        assert.ok(!err);
        assert.equal(objs.length, 2);
        var names = objs.map(function(o) { return o.name });
        assert.ok(names.indexOf('Jon') === -1);
        assert.ok(names.indexOf('Neil') >= 0);
        assert.ok(names.indexOf('Katie') >= 0);
        assert.ok(names.indexOf('Belinda') === -1);
        done();
      });
    }

    async.series([createObjs, findObjs], done);
  });

  it('should properly align count columns', function(done) {
    db.query("start n=node(*) return count(n) as totalNodes", function(e, res) {
      assert(!e);
      assert(!Array.isArray(res));
      assert(res.totalNodes);
      done()
    });
  });
});
<|endoftext|>


<commit_before>var util = require('util'),
    events = require('events'),
    should = require('should'),
    _ = require('underscore'),
    Radio = require('../lib/radio');

/**
 * Builds an instance of EventEmitter class
 */
var getEventEmitter = function() {
    // Temporary constructor is used to prevent modifications of EventEmitter
    // prototype.
    var Tmp = function() {
        events.EventEmitter.call(this);
    };

    util.inherits(Tmp, events.EventEmitter);

    return new Tmp();
};

/**
 * Creates a valid radio station.
 *
 * @returns {Object}
 */
var getRadioStation = function() {
    return {
        id: 'foo',
        name: 'Foo FM',
        url: 'http://foofm.com'
    };
};

/**
 * Records all commands that are run with MPD client stub.
 *
 * Actually this is only a decorator for MPD client stub.
 *
 * @param {Object} client MPD client stub returned by "getMpdClient"
 * @returns {Object} Modified MPD client stub with additional
 * "getRecordedCommands" method.
 */
var recordCommands = function(client) {
    client._commands = [];

    var sendCommand = client.sendCommand;
    client.sendCommand = function(command, callback) {
        client._commands.push(command);
        // Run the original method.
        sendCommand.call(this, command, callback);
    };

    var sendCommands = client.sendCommands;
    client.sendCommands = function(commands, callback) {
        commands.forEach(function(command) {
            this._commands.push(command);
        }, this);
        // Run the original method
        sendCommands.call(this, commands, callback);
    };

    // This is not a part of MpdClient and is used spy for commands calls.
    client.getRecordedCommands = function() {
        return this._commands;
    };

    return client;
};

/**
 * Builds a stub that mimics MpdClient behavior.
 *
 * The main purpose of this stub is to record commands that send to mpd.
 *
 * @returns {Object}
 */
var getMpdClient = function() {
    var noop = function() {},
        client = getEventEmitter();

    client.sendCommand = function(command, callback) {
        // Make sure stub works fine even if there is no callback specified
        var done = callback || noop;
        done(null);
    };

    client.sendCommands = function(commands, callback) {
        // Make sure stub works fine even if there is no callback specified
        var done = callback || noop;
        done(null);
    };

    return client;
};

/**
 * Builds a stub which mimics MpdPool behavior.
 *
 * @param {Object} client An object which will be returned on every call to
 * mock's "getClient" method. By default a stub for mpd client is used.
 * @returns {Object}
 */
var getMpdPool = function(client) {
    var pool = getEventEmitter();

    if (typeof client === 'undefined') {
        client = getMpdClient();
    }

    // Define API methods to mimic real MpdPool instance.
    pool.getClient = function(callback) {
        if (client) {
            return callback(null, client);
        } else {
            return callback(new Error('The client is not specified'));
        }
    };

    pool.isConnected = function() {
        return !!client;
    };

    if (client) {
        process.nextTick(function() {
            pool.emit('connect', client);
        });
    }

    return pool;
};

describe('Radio', function() {
    describe('setCurrentStation', function() {
        var radio = null;

        beforeEach(function() {
            radio = new Radio(getMpdPool());
        });

        afterEach(function() {
            radio = null;
        });

        it('should throw error on station with missed keys', function() {
            var station = getRadioStation();

            // Make sure that the error is throw when exactly one field is
            // missed.
            Object.keys(station).forEach(function(key) {
                (function() {
                    radio.setCurrentStation(_.omit(station, [key]));
                }).should.throw(util.format(
                    'These keys are missed at station object: "%s"',
                    key
                ));
            });

            // Make sure an error is thrown when several keys are missed
            (function() {
                radio.setCurrentStation(_.omit(station, ['id', 'name']));
            }).should.throw(/^These keys are missed/);

            // Make sure an error is thrown when empty object is used as a
            // station
            (function() {
                radio.setCurrentStation({});
            }).should.throw(/^These keys are missed/);
        });

        it('should works fine with a valid station', function() {
            (function() {
                radio.setCurrentStation(getRadioStation());
            }).should.not.throw();
        });

        it('should works fine on valid station with extra keys', function() {
            (function() {
                var station = getRadioStation();
                station.fooBar = 'Extra field';

                radio.setCurrentStation(station);
            }).should.not.throw();
        });
    });

    describe('getCurrentStation', function() {
        it('should return false when no current station was set', function() {
            var radio = new Radio(getMpdPool());
            radio.getCurrentStation().should.be.false();
        });

        it('should return previously set station', function() {
            var radio = new Radio(getMpdPool());

            radio.setCurrentStation(getRadioStation());
            radio.getCurrentStation().should.be.eql(getRadioStation());
        });
    });

    describe('play', function() {
        it('should return error if no station is selected', function(done) {
            var radio = new Radio(getMpdPool());
            radio.play(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('You should choose a station before play it.');

                done();
            });
        });

        it('should send "setvol", "add" and "play" commands to MPD server', function(done) {
            var client = recordCommands(getMpdClient()),
                station = getRadioStation(),
                radio = new Radio(getMpdPool(client));

            radio.setCurrentStation(station);
            radio.play(function(err) {
                should(err).be.null();
                var commands = client.getRecordedCommands();

                // Make sure the commands are in the correct order and have
                // correct arguments.
                commands.length.should.be.equal(3);
                commands[0].name.should.be.equal('setvol');
                commands[0].args[0].should.be.equal(100);
                commands[1].name.should.be.equal('add');
                commands[1].args[0].should.be.equal(station.url);
                commands[2].name.should.be.equal('play');

                done();
            });
        });

        it('should return MPD client error "as is"', function(done) {
            var client = getMpdClient();

            // Make sure client return an error
            client.sendCommands = client.sendCommand = function(cmd, callback) {
                callback(new Error('Test'));
            };

            var radio = new Radio(getMpdPool(client)),
                station = getRadioStation();

            radio.setCurrentStation(station);
            radio.play(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('Test');

                done();
            });
        });

        it('should return error if MPD client cannot be got', function(done) {
            var radio = new Radio(getMpdPool(null));

            radio.setCurrentStation(getRadioStation());
            radio.play(function(err) {
                err.should.be.Error();
                // This message is defined in MpdPool stub.
                err.message.should.be.equal('The client is not specified');

                done();
            });
        });
    });

    describe('stop', function() {
        it('should send "stop" and "clear" commands to MPD server', function(done) {
            var client = recordCommands(getMpdClient()),
                radio = new Radio(getMpdPool(client));

            radio.stop(function(err) {
                should(err).be.null();

                var commands = client.getRecordedCommands();
                commands.length.should.be.equal(2);
                commands[0].name.should.be.equal('stop');
                commands[1].name.should.be.equal('clear');

                done();
            });
        });

        it('should return MPD client error "as is"', function(done) {
            var client = getMpdClient();

            // Make sure client return an error
            client.sendCommands = client.sendCommand = function(cmd, callback) {
                callback(new Error('Test'));
            };

            var radio = new Radio(getMpdPool(client)),
                station = getRadioStation();

            radio.setCurrentStation(station);
            radio.stop(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('Test');

                done();
            });
        });

        it('should return error if MPD client cannot be got', function(done) {
            var radio = new Radio(getMpdPool(null));

            radio.setCurrentStation(getRadioStation());
            radio.stop(function(err) {
                err.should.be.Error();
                // This message is defined in MpdPool stub.
                err.message.should.be.equal('The client is not specified');

                done();
            });
        });
    });
});
<commit_msg>Make MpdClient stuv mimic all events<commit_after>var util = require('util'),
    events = require('events'),
    should = require('should'),
    _ = require('underscore'),
    Radio = require('../lib/radio');

/**
 * Builds an instance of EventEmitter class
 */
var getEventEmitter = function() {
    // Temporary constructor is used to prevent modifications of EventEmitter
    // prototype.
    var Tmp = function() {
        events.EventEmitter.call(this);
    };

    util.inherits(Tmp, events.EventEmitter);

    return new Tmp();
};

/**
 * Creates a valid radio station.
 *
 * @returns {Object}
 */
var getRadioStation = function() {
    return {
        id: 'foo',
        name: 'Foo FM',
        url: 'http://foofm.com'
    };
};

/**
 * Records all commands that are run with MPD client stub.
 *
 * Actually this is only a decorator for MPD client stub.
 *
 * @param {Object} client MPD client stub returned by "getMpdClient"
 * @returns {Object} Modified MPD client stub with additional
 * "getRecordedCommands" method.
 */
var recordCommands = function(client) {
    client._commands = [];

    var sendCommand = client.sendCommand;
    client.sendCommand = function(command, callback) {
        client._commands.push(command);
        // Run the original method.
        sendCommand.call(this, command, callback);
    };

    var sendCommands = client.sendCommands;
    client.sendCommands = function(commands, callback) {
        commands.forEach(function(command) {
            this._commands.push(command);
        }, this);
        // Run the original method
        sendCommands.call(this, commands, callback);
    };

    // This is not a part of MpdClient and is used spy for commands calls.
    client.getRecordedCommands = function() {
        return this._commands;
    };

    return client;
};

/**
 * Builds a stub that mimics MpdClient behavior.
 *
 * The main purpose of this stub is to record commands that send to mpd.
 *
 * @returns {Object}
 */
var getMpdClient = function() {
    var noop = function() {},
        client = getEventEmitter();

    client.sendCommand = function(command, callback) {
        // Make sure stub works fine even if there is no callback specified
        var done = callback || noop;
        done(null);
    };

    client.sendCommands = function(commands, callback) {
        // Make sure stub works fine even if there is no callback specified
        var done = callback || noop;
        done(null);
    };

    // Make sure all events are emitted.
    process.nextTick(function() {
        client.emit('connect');
        process.nextTick(function() {
            client.emit('ready');
        });
    });

    return client;
};

/**
 * Builds a stub which mimics MpdPool behavior.
 *
 * @param {Object} client An object which will be returned on every call to
 * mock's "getClient" method. By default a stub for mpd client is used.
 * @returns {Object}
 */
var getMpdPool = function(client) {
    var pool = getEventEmitter();

    if (typeof client === 'undefined') {
        client = getMpdClient();
    }

    // Define API methods to mimic real MpdPool instance.
    pool.getClient = function(callback) {
        if (client) {
            return callback(null, client);
        } else {
            return callback(new Error('The client is not specified'));
        }
    };

    pool.isConnected = function() {
        return !!client;
    };

    if (client instanceof events.EventEmitter) {
        client.on('connect', function() {
            pool.emit('connect', client);
        });
    }

    return pool;
};

describe('Radio', function() {
    describe('setCurrentStation', function() {
        var radio = null;

        beforeEach(function() {
            radio = new Radio(getMpdPool());
        });

        afterEach(function() {
            radio = null;
        });

        it('should throw error on station with missed keys', function() {
            var station = getRadioStation();

            // Make sure that the error is throw when exactly one field is
            // missed.
            Object.keys(station).forEach(function(key) {
                (function() {
                    radio.setCurrentStation(_.omit(station, [key]));
                }).should.throw(util.format(
                    'These keys are missed at station object: "%s"',
                    key
                ));
            });

            // Make sure an error is thrown when several keys are missed
            (function() {
                radio.setCurrentStation(_.omit(station, ['id', 'name']));
            }).should.throw(/^These keys are missed/);

            // Make sure an error is thrown when empty object is used as a
            // station
            (function() {
                radio.setCurrentStation({});
            }).should.throw(/^These keys are missed/);
        });

        it('should works fine with a valid station', function() {
            (function() {
                radio.setCurrentStation(getRadioStation());
            }).should.not.throw();
        });

        it('should works fine on valid station with extra keys', function() {
            (function() {
                var station = getRadioStation();
                station.fooBar = 'Extra field';

                radio.setCurrentStation(station);
            }).should.not.throw();
        });
    });

    describe('getCurrentStation', function() {
        it('should return false when no current station was set', function() {
            var radio = new Radio(getMpdPool());
            radio.getCurrentStation().should.be.false();
        });

        it('should return previously set station', function() {
            var radio = new Radio(getMpdPool());

            radio.setCurrentStation(getRadioStation());
            radio.getCurrentStation().should.be.eql(getRadioStation());
        });
    });

    describe('play', function() {
        it('should return error if no station is selected', function(done) {
            var radio = new Radio(getMpdPool());
            radio.play(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('You should choose a station before play it.');

                done();
            });
        });

        it('should send "setvol", "add" and "play" commands to MPD server', function(done) {
            var client = recordCommands(getMpdClient()),
                station = getRadioStation(),
                radio = new Radio(getMpdPool(client));

            radio.setCurrentStation(station);
            radio.play(function(err) {
                should(err).be.null();
                var commands = client.getRecordedCommands();

                // Make sure the commands are in the correct order and have
                // correct arguments.
                commands.length.should.be.equal(3);
                commands[0].name.should.be.equal('setvol');
                commands[0].args[0].should.be.equal(100);
                commands[1].name.should.be.equal('add');
                commands[1].args[0].should.be.equal(station.url);
                commands[2].name.should.be.equal('play');

                done();
            });
        });

        it('should return MPD client error "as is"', function(done) {
            var client = getMpdClient();

            // Make sure client return an error
            client.sendCommands = client.sendCommand = function(cmd, callback) {
                callback(new Error('Test'));
            };

            var radio = new Radio(getMpdPool(client)),
                station = getRadioStation();

            radio.setCurrentStation(station);
            radio.play(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('Test');

                done();
            });
        });

        it('should return error if MPD client cannot be got', function(done) {
            var radio = new Radio(getMpdPool(null));

            radio.setCurrentStation(getRadioStation());
            radio.play(function(err) {
                err.should.be.Error();
                // This message is defined in MpdPool stub.
                err.message.should.be.equal('The client is not specified');

                done();
            });
        });
    });

    describe('stop', function() {
        it('should send "stop" and "clear" commands to MPD server', function(done) {
            var client = recordCommands(getMpdClient()),
                radio = new Radio(getMpdPool(client));

            radio.stop(function(err) {
                should(err).be.null();

                var commands = client.getRecordedCommands();
                commands.length.should.be.equal(2);
                commands[0].name.should.be.equal('stop');
                commands[1].name.should.be.equal('clear');

                done();
            });
        });

        it('should return MPD client error "as is"', function(done) {
            var client = getMpdClient();

            // Make sure client return an error
            client.sendCommands = client.sendCommand = function(cmd, callback) {
                callback(new Error('Test'));
            };

            var radio = new Radio(getMpdPool(client)),
                station = getRadioStation();

            radio.setCurrentStation(station);
            radio.stop(function(err) {
                err.should.be.Error();
                err.message.should.be.equal('Test');

                done();
            });
        });

        it('should return error if MPD client cannot be got', function(done) {
            var radio = new Radio(getMpdPool(null));

            radio.setCurrentStation(getRadioStation());
            radio.stop(function(err) {
                err.should.be.Error();
                // This message is defined in MpdPool stub.
                err.message.should.be.equal('The client is not specified');

                done();
            });
        });
    });
});
<|endoftext|>


<commit_before>define(["vendors/go-dashboard/dashboard","vendors/go-dashboard/jquery.qrcode.min"], function(Dashboard, qrcode) {

    $.fn.dashboard = function (options) {
        var el = $(this).find(".dashboard");
        if (!$(el).data("dashboard")) {
            $(el).data("dashboard", true);

            var DashboardViewModel = new Dashboard();
            var __has_loaded = false;
            // catch click tab title
            $(el).find(".dashboard-scene>ul").click(function (event) {
                if ($(event.target).is("a")) {

                    if (!__has_loaded) {
                        var base_dir = window.location.pathname;
                        base_dir = base_dir.substring(0, base_dir.lastIndexOf("/") + 1)
                        $("html script[src]").each(function (i, e) {
                            var src = $(e).attr("src");
                            if (src) {
                                if (src.substr(0, 1) != "/") {
                                    src = base_dir + src;
                                }
                                DashboardViewModel.availableScripts.push(src);
                            }
                        });
                        $("html link[href]").each(function (i, e) {
                            if ($(e).attr("rel") == "stylesheet" || $(e).attr("type") == "text/css") {
                                var src = $(e).attr("href");
                                if (src) {
                                    if (src.substr(0, 1) != "/") {
                                        src = base_dir + src;
                                    }
                                    DashboardViewModel.availableStyles.push(src);
                                }
                            }
                        });

                        var clean_html = function(_content){
                            var re = /").appendTo(el);
        }
        return $._delay(function (dfd) {
            /*
             //     anon       true, if the space may be omitted in anonymous function declarations
             //     bitwise    true, if bitwise operators should be allowed
             //     browser    true, if the standard browser globals should be predefined
             //     'continue' true, if the continuation statement should be tolerated
             //     css        true, if CSS workarounds should be tolerated
             //     debug      true, if debugger statements should be allowed
             //     devel      true, if logging should be allowed (console, alert, etc.)
             //     eqeq       true, if == should be allowed
             //     es5        true, if ES5 syntax should be allowed
             //     evil       true, if eval should be allowed
             //     forin      true, if for in statements need not filter
             //     fragment   true, if HTML fragments should be allowed
             //     indent     the indentation factor
             //     maxerr     the maximum number of errors to allow
             //     maxlen     the maximum length of a source line
             //     newcap     true, if constructor names capitalization is ignored
             //     node       true, if Node.js globals should be predefined
             //     nomen      true, if names may have dangling _
             //     on         true, if HTML event handlers should be allowed
             //     passfail   true, if the scan should stop on first error
             //     plusplus   true, if increment/decrement should be allowed
             //     properties true, if all property names must be declared with / *properties* /
             //     regexp     true, if the . should be allowed in regexp literals
             //     rhino      true, if the Rhino environment globals should be predefined
             //     undef      true, if variables can be declared out of order
             //     unparam    true, if unused parameters should be tolerated
             //     sloppy     true, if the 'use strict'; pragma is optional
             //     stupid     true, if really stupid practices are tolerated
             //     sub        true, if all forms of subscript notation are tolerated
             //     todo       true, if TODO comments are tolerated
             //     vars       true, if multiple var statements per function should be allowed
             //     white      true, if sloppy whitespace is tolerated
             //     windows    true, if MS Windows-specific globals should be predefined

             //      to be configured
             */

            var myResult = JSLINT(script_content, {"predef": []});
            if (myResult == false) {
                var myReport = JSLINT.errors;
                //var myErrorReport = JSLINT.error_report(data);
                //var myPropertyReport = JSLINT.properties_report(JSLINT.property);

                var messages = new Array();
                for (i = 0, len = myReport.length; i < len; i++) {
                    var m = myReport[i];
                    if (m != null) {
                        messages.push({
                            "message": m.reason,
                            "id": m.id,
                            "line": m.line,
                            "col": m.character,
                            "evidence": m.evidence
                        });
                    }
                }
                dfd.resolve(messages);
            } else {
                dfd.resolve([]);
            }
        });
    };
    var create_jshint_report = function(el, script_content, callback){
        require(["vendors/go-jshint/jshint-1.1.0"], function(  ){
            var options = {};
            var globals = {};
            var success = JSHINT(script_content, options, globals);
            if (success == false) {
                var myReport = JSHINT.errors;
                //var myErrorReport = JSLINT.error_report(data);
                //var myPropertyReport = JSLINT.properties_report(JSLINT.property);

                var messages = new Array();
                for (i = 0, len = myReport.length; i < len; i++) {
                    var m = myReport[i];
                    if (m != null) {
                        messages.push({
                            "message": m.reason,
                            "id": m.id,
                            "line": m.line,
                            "col": m.character,
                            "evidence": m.evidence
                        });
                    }
                }
                callback(messages);
            }
        });
    }
    var create_csslint_report = function (el, css_content) {

        var lib_src = "/js/vendors/go-csslint/csslint.js";
        if ($("body").find("script[src='" + lib_src + "']").length == 0) {
            $("").appendTo(el);
        }
        return $._delay(function (dfd) {

            var results = CSSLint.verify(css_content);
            var messages = new Array();

            for (i = 0, len = results.messages.length; i < len; i++) {
                var m = results.messages[i];
                messages.push({
                    "message": m.message + " (line " + m.line + ", col " + m.col + ")",
                    "level": m.type
                });
            }

            dfd.resolve(messages);
        });
    };

	return $
});
<commit_msg>Fixed backslash removal under windows environment<commit_after>define(["vendors/go-dashboard/dashboard","vendors/go-dashboard/jquery.qrcode.min"], function(Dashboard, qrcode) {

    $.fn.dashboard = function (options) {
        var el = $(this).find(".dashboard");
        if (!$(el).data("dashboard")) {
            $(el).data("dashboard", true);

            var DashboardViewModel = new Dashboard();
            var __has_loaded = false;
            // catch click tab title
            $(el).find(".dashboard-scene>ul").click(function (event) {
                if ($(event.target).is("a")) {

                    if (!__has_loaded) {
                        var base_dir = window.location.pathname;
                        base_dir = base_dir.substring(0, base_dir.lastIndexOf("/") + 1)
                        $("html script[src]").each(function (i, e) {
                            var src = $(e).attr("src");
                            if (src) {
                                src = src.replace(/\\/g,"/");
                                src = src.replace("//","/");
                                src = src.replace("//","/");
                                if (src.substr(0, 1) != "/") {
                                    src = base_dir + src;
                                }
                                DashboardViewModel.availableScripts.push(src);
                            }
                        });
                        $("html link[href]").each(function (i, e) {
                            if ($(e).attr("rel") == "stylesheet" || $(e).attr("type") == "text/css") {
                                var src = $(e).attr("href");
                                if (src) {
                                    src = src.replace(/\\/g,"/");
                                    src = src.replace("//","/");
                                    src = src.replace("//","/");
                                    if (src.substr(0, 1) != "/") {
                                        src = base_dir + src;
                                    }
                                    DashboardViewModel.availableStyles.push(src);
                                }
                            }
                        });

                        var clean_html = function(_content){
                            var re = /").appendTo(el);
        }
        return $._delay(function (dfd) {
            /*
             //     anon       true, if the space may be omitted in anonymous function declarations
             //     bitwise    true, if bitwise operators should be allowed
             //     browser    true, if the standard browser globals should be predefined
             //     'continue' true, if the continuation statement should be tolerated
             //     css        true, if CSS workarounds should be tolerated
             //     debug      true, if debugger statements should be allowed
             //     devel      true, if logging should be allowed (console, alert, etc.)
             //     eqeq       true, if == should be allowed
             //     es5        true, if ES5 syntax should be allowed
             //     evil       true, if eval should be allowed
             //     forin      true, if for in statements need not filter
             //     fragment   true, if HTML fragments should be allowed
             //     indent     the indentation factor
             //     maxerr     the maximum number of errors to allow
             //     maxlen     the maximum length of a source line
             //     newcap     true, if constructor names capitalization is ignored
             //     node       true, if Node.js globals should be predefined
             //     nomen      true, if names may have dangling _
             //     on         true, if HTML event handlers should be allowed
             //     passfail   true, if the scan should stop on first error
             //     plusplus   true, if increment/decrement should be allowed
             //     properties true, if all property names must be declared with / *properties* /
             //     regexp     true, if the . should be allowed in regexp literals
             //     rhino      true, if the Rhino environment globals should be predefined
             //     undef      true, if variables can be declared out of order
             //     unparam    true, if unused parameters should be tolerated
             //     sloppy     true, if the 'use strict'; pragma is optional
             //     stupid     true, if really stupid practices are tolerated
             //     sub        true, if all forms of subscript notation are tolerated
             //     todo       true, if TODO comments are tolerated
             //     vars       true, if multiple var statements per function should be allowed
             //     white      true, if sloppy whitespace is tolerated
             //     windows    true, if MS Windows-specific globals should be predefined

             //      to be configured
             */

            var myResult = JSLINT(script_content, {"predef": []});
            if (myResult == false) {
                var myReport = JSLINT.errors;
                //var myErrorReport = JSLINT.error_report(data);
                //var myPropertyReport = JSLINT.properties_report(JSLINT.property);

                var messages = new Array();
                for (i = 0, len = myReport.length; i < len; i++) {
                    var m = myReport[i];
                    if (m != null) {
                        messages.push({
                            "message": m.reason,
                            "id": m.id,
                            "line": m.line,
                            "col": m.character,
                            "evidence": m.evidence
                        });
                    }
                }
                dfd.resolve(messages);
            } else {
                dfd.resolve([]);
            }
        });
    };
    var create_jshint_report = function(el, script_content, callback){
        require(["vendors/go-jshint/jshint-1.1.0"], function(  ){
            var options = {};
            var globals = {};
            var success = JSHINT(script_content, options, globals);
            if (success == false) {
                var myReport = JSHINT.errors;
                //var myErrorReport = JSLINT.error_report(data);
                //var myPropertyReport = JSLINT.properties_report(JSLINT.property);

                var messages = new Array();
                for (i = 0, len = myReport.length; i < len; i++) {
                    var m = myReport[i];
                    if (m != null) {
                        messages.push({
                            "message": m.reason,
                            "id": m.id,
                            "line": m.line,
                            "col": m.character,
                            "evidence": m.evidence
                        });
                    }
                }
                callback(messages);
            }
        });
    }
    var create_csslint_report = function (el, css_content) {

        var lib_src = "/js/vendors/go-csslint/csslint.js";
        if ($("body").find("script[src='" + lib_src + "']").length == 0) {
            $("").appendTo(el);
        }
        return $._delay(function (dfd) {

            var results = CSSLint.verify(css_content);
            var messages = new Array();

            for (i = 0, len = results.messages.length; i < len; i++) {
                var m = results.messages[i];
                messages.push({
                    "message": m.message + " (line " + m.line + ", col " + m.col + ")",
                    "level": m.type
                });
            }

            dfd.resolve(messages);
        });
    };

    return $
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @module query/stops
 */

const Stop = require('../gtfs/Stop.js');

// ==== CALLBACK FUNCTIONS ==== //

/**
 * This callback is performed after a single Stop has been
 * selected from the database.
 * @callback getStopCallback
 * @param {Error} error Database Query Error
 * @param {Stop} [stop] The selected Stop
 */

/**
 * This callback is performed after multiple Stops have been
 * selected from the database.
 * @callback getStopsCallback
 * @param {Error} error Database Query Error
 * @param {Stop[]} [stops] An array of the selected Stops
 */

// ==== QUERY FUNCTIONS ==== //

/**
 * Get the Stop(s) specified by ID(s) from the passed database
 *
 * **package:** core.query.stops.getStop()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string|string[]} id Stop ID(s)
 * @param {getStopCallback|getStopsCallback} callback getStop(s) callback function
 */
function getStop(db, id, callback) {

  // Build stop id string
  let stopIds = "";
  if ( typeof id === 'string' || typeof id === 'number' ) {
    stopIds = "('" + id + "')";
  }
  else {
    try {
      stopIds = "('" + id.join("', '") + "')";
    }
    catch(err) {
      console.warn(err);
      stopIds = "('" + id + "')";
    }
  }

  // Build Select Statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "gtfs_stops.stop_id IN " + stopIds + ";";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // List of Stops to return
    let rtn = [];

    // Parse each returned stop...
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if (row.display_name !== undefined && row.display_name !== "") {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if (row.wheelchair_boarding === null) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to list
      rtn.push(stop);

    }

    // No Stop(s) found, return undefined
    if ( rtn.length === 0 ) {
      return callback(null, undefined);
    }

    // Return Stop(s)
    else if ( rtn.length === 1 ) {
      return callback(null, rtn[0]);
    }
    else {
      return callback(null, rtn);
    }

  });

}

// TODO: Break this up into multiple functions?
/**
 * Get the Stop specified by name from the passed database
 *
 * **package:** core.query.stops.getStopByName()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} name Stop Name (found in either gtfs_stops, rt_alt_stop_names or rt_stops_extra tables)
 * @param {getStopCallback} callback getStop callback function
 */
function getStopByName(db, name, callback) {

  // ==== GTFS_STOPS ==== //

  // Get stop id for name in gtfs_stops
  let select = "SELECT stop_id FROM gtfs_stops WHERE stop_name='" + name + "' COLLATE NOCASE;";

  // Query database for stop
  db.get(select, function(err, result) {

    // Database Error
    if ( err ) {
      return callback(err);
    }

    // Found a Stop, return
    if ( result !== undefined ) {
      return getStop(db, result.stop_id, callback);
    }

    // ==== RT_ALT_STOP_NAMES ==== //

    // Check rt_alt_stop_names for stop name
    let select = "SELECT stop_id FROM rt_alt_stop_names WHERE alt_stop_name='" + name + "' COLLATE NOCASE;";

    // Query database for stop
    db.get(select, function(err, result) {

      // Database Error
      if ( err ) {
        return callback(err);
      }

      // Found a Stop, return
      if ( result !== undefined ) {
        return getStop(db, result.stop_id, callback);
      }

      // ==== RT_STOPS_EXTRA ==== //

      // Check rt_stops_extra for stop name
      let select = "SELECT stop_id FROM rt_stops_extra WHERE display_name='" + name + "' COLLATE NOCASE;";

      // Query the database for stop
      db.get(select, function(err, result) {

        // Database Error
        if ( err ) {
          return callback(err);
        }

        // Found a Stop, return Stop
        if ( result !== undefined ) {
          return getStop(db, result.stop_id, callback);
        }

        // No Stop Found, return undefined
        else {
          return callback(null, undefined);
        }

      });

    });

  });

}

/**
 * Get the Stop specified by status id from the passed database
 *
 * **package:** core.query.stops.getStopByStatusId()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} statusId the status id of the Stop
 * @param {getStopCallback} callback getStop callback function
 */
function getStopByStatusId(db, statusId, callback) {

  // Make sure statusId is not -1
  if ( statusId === "-1" ) {
    return callback(
      new Error('Stop does not have a valid Status ID')
    );
  }

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "rt_stops_extra.status_id='" + statusId + "';";

  // Query the database
  db.get(select, function(err, result) {

    // Database Error
    if ( err ) {
      return callback(err);
    }

    // No Stop found...
    if ( result === undefined ) {
      return callback(null, undefined);
    }

    // Use rt display_name if provided
    let final_name = result.stop_name;
    if ( result.display_name !== undefined && result.display_name !== "" ) {
      final_name = result.display_name;
    }

    // Set wheelchair boarding to unknown if status not in database
    let wheelchair_boarding = result.wheelchair_boarding;
    if ( result.wheelchair_boarding === null ) {
      wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
    }

    // build the Stop
    let stop = new Stop(
      result.stop_id,
      final_name,
      result.stop_lat,
      result.stop_lon,
      result.stop_url,
      wheelchair_boarding,
      result.status_id,
      result.transfer_weight
    );

    // return the Stop in the callback
    return callback(null, stop);

  });

}

/**
 * Get all of the Stops that are stored in the passed database
 * (sorted alphabetically)
 *
 * **package:** core.query.stops.getStops()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {getStopsCallback} callback getStops callback function
 */
function getStops(db, callback) {

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Array to hold stops to return
    let stops = [];

    // Parse each row
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if ( row.display_name !== undefined && row.display_name !== "" ) {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if ( row.wheelchair_boarding === null ) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to return array
      stops.push(stop);

    }

    // return the stops in the callback
    stops.sort(Stop.sortByName);
    return callback(null, stops);

  });

}

/**
 * Get all of the Stops that are associated with the
 * specified route (sorted alphabetically)
 *
 * **package:** core.query.stops.getStopsByRoute()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} routeId The Route ID
 * @param {getStopsCallback} callback getStops callback function
 */
function getStopsByRoute(db, routeId, callback) {

  // Build select statement
  // Get all Stop IDs that have a trip that uses the specified route
  let select = "SELECT DISTINCT stop_id FROM gtfs_stop_times WHERE trip_id IN " +
    "(SELECT DISTINCT trip_id FROM gtfs_trips WHERE route_id='" + routeId + "');";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Stop IDs to get
    let stopIds = [];

    // Parse each row of the results
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];
      stopIds.push(row.stop_id);
    }

    // Build stops from the stop ids
    getStop(db, stopIds, function(err, stops) {

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

      // Return sorted stops with callback
      if ( stops !== undefined ) {
        stops.sort(Stop.sortByName);
      }
      return callback(null, stops);

    });

  });

}

/**
 * Get all of the Stops with a valid (!= -1) Status ID that are stored
 * in the passed database (sorted alphabetically)
 *
 * **package:** core.query.stops.getStopsWithStatus()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {getStopsCallback} callback getStops callback function
 */
function getStopsWithStatus(db, callback) {

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "rt_stops_extra.status_id <> '-1';";

  // Query the database
  db.select(select, function(err, results) {

    // Database query error
    if ( err ) {
      return callback(err);
    }

    // Array to hold stops to return
    let stops = [];

    // Parse each row
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if ( row.display_name !== undefined && row.display_name !== "" ) {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if ( row.wheelchair_boarding === null ) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to return array
      stops.push(stop);

    }

    // return the stops in the callback
    stops.sort(Stop.sortByName);
    return callback(null, stops);

  });

}

// Export Functions
module.exports = {
  getStop: getStop,
  getStopByName: getStopByName,
  getStopByStatusId: getStopByStatusId,
  getStops: getStops,
  getStopsByRoute: getStopsByRoute,
  getStopsWithStatus: getStopsWithStatus
};<commit_msg>Query Stops: todo items<commit_after>'use strict';

/**
 * @module query/stops
 */

const Stop = require('../gtfs/Stop.js');

// ==== CALLBACK FUNCTIONS ==== //

/**
 * This callback is performed after a single Stop has been
 * selected from the database.
 * @callback getStopCallback
 * @param {Error} error Database Query Error
 * @param {Stop} [stop] The selected Stop
 */

/**
 * This callback is performed after multiple Stops have been
 * selected from the database.
 * @callback getStopsCallback
 * @param {Error} error Database Query Error
 * @param {Stop[]} [stops] An array of the selected Stops
 */

// ==== QUERY FUNCTIONS ==== //

/**
 * Get the Stop(s) specified by ID(s) from the passed database
 *
 * **package:** core.query.stops.getStop()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string|string[]} id Stop ID(s)
 * @param {getStopCallback|getStopsCallback} callback getStop(s) callback function
 */
function getStop(db, id, callback) {

  // Build stop id string
  let stopIds = "";
  if ( typeof id === 'string' || typeof id === 'number' ) {
    stopIds = "('" + id + "')";
  }
  else {
    try {
      stopIds = "('" + id.join("', '") + "')";
    }
    catch(err) {
      console.warn(err);
      stopIds = "('" + id + "')";
    }
  }

  // Build Select Statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "gtfs_stops.stop_id IN " + stopIds + ";";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // List of Stops to return
    let rtn = [];

    // Parse each returned stop...
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if (row.display_name !== undefined && row.display_name !== "") {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if (row.wheelchair_boarding === null) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to list
      rtn.push(stop);

    }

    // No Stop(s) found, return undefined
    if ( rtn.length === 0 ) {
      return callback(null, undefined);
    }

    // Return Stop(s)
    else if ( rtn.length === 1 ) {
      return callback(null, rtn[0]);
    }
    else {
      return callback(null, rtn);
    }

  });

}

// TODO: Break this up into multiple functions?
/**
 * Get the Stop specified by name from the passed database
 *
 * **package:** core.query.stops.getStopByName()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} name Stop Name (found in either gtfs_stops, rt_alt_stop_names or rt_stops_extra tables)
 * @param {getStopCallback} callback getStop callback function
 */
function getStopByName(db, name, callback) {

  // ==== GTFS_STOPS ==== //

  // Get stop id for name in gtfs_stops
  let select = "SELECT stop_id FROM gtfs_stops WHERE stop_name='" + name + "' COLLATE NOCASE;";

  // Query database for stop
  db.get(select, function(err, result) {

    // Database Error
    if ( err ) {
      return callback(err);
    }

    // Found a Stop, return
    if ( result !== undefined ) {
      return getStop(db, result.stop_id, callback);
    }

    // ==== RT_ALT_STOP_NAMES ==== //

    // Check rt_alt_stop_names for stop name
    let select = "SELECT stop_id FROM rt_alt_stop_names WHERE alt_stop_name='" + name + "' COLLATE NOCASE;";

    // Query database for stop
    db.get(select, function(err, result) {

      // Database Error
      if ( err ) {
        return callback(err);
      }

      // Found a Stop, return
      if ( result !== undefined ) {
        return getStop(db, result.stop_id, callback);
      }

      // ==== RT_STOPS_EXTRA ==== //

      // Check rt_stops_extra for stop name
      let select = "SELECT stop_id FROM rt_stops_extra WHERE display_name='" + name + "' COLLATE NOCASE;";

      // Query the database for stop
      db.get(select, function(err, result) {

        // Database Error
        if ( err ) {
          return callback(err);
        }

        // Found a Stop, return Stop
        if ( result !== undefined ) {
          return getStop(db, result.stop_id, callback);
        }

        // No Stop Found, return undefined
        else {
          return callback(null, undefined);
        }

      });

    });

  });

}

/**
 * Get the Stop specified by status id from the passed database
 *
 * **package:** core.query.stops.getStopByStatusId()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} statusId the status id of the Stop
 * @param {getStopCallback} callback getStop callback function
 */
function getStopByStatusId(db, statusId, callback) {

  // Make sure statusId is not -1
  if ( statusId === "-1" ) {
    return callback(
      new Error('Stop does not have a valid Status ID')
    );
  }

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "rt_stops_extra.status_id='" + statusId + "';";

  // Query the database
  db.get(select, function(err, result) {

    // Database Error
    if ( err ) {
      return callback(err);
    }

    // No Stop found...
    if ( result === undefined ) {
      return callback(null, undefined);
    }

    // Use rt display_name if provided
    let final_name = result.stop_name;
    if ( result.display_name !== undefined && result.display_name !== "" ) {
      final_name = result.display_name;
    }

    // Set wheelchair boarding to unknown if status not in database
    let wheelchair_boarding = result.wheelchair_boarding;
    if ( result.wheelchair_boarding === null ) {
      wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
    }

    // build the Stop
    let stop = new Stop(
      result.stop_id,
      final_name,
      result.stop_lat,
      result.stop_lon,
      result.stop_url,
      wheelchair_boarding,
      result.status_id,
      result.transfer_weight
    );

    // return the Stop in the callback
    return callback(null, stop);

  });

}

/**
 * Get all of the Stops that are stored in the passed database
 * (sorted alphabetically)
 *
 * **package:** core.query.stops.getStops()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {getStopsCallback} callback getStops callback function
 */
function getStops(db, callback) {

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Array to hold stops to return
    let stops = [];

    // Parse each row
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if ( row.display_name !== undefined && row.display_name !== "" ) {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if ( row.wheelchair_boarding === null ) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to return array
      stops.push(stop);

    }

    // return the stops in the callback
    stops.sort(Stop.sortByName);
    return callback(null, stops);

  });

}

/**
 * Get all of the Stops that are associated with the
 * specified route (sorted alphabetically)
 *
 * **package:** core.query.stops.getStopsByRoute()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {string} routeId The Route ID
 * @param {getStopsCallback} callback getStops callback function
 */
function getStopsByRoute(db, routeId, callback) {

  // Build select statement
  // Get all Stop IDs that have a trip that uses the specified route
  let select = "SELECT DISTINCT stop_id FROM gtfs_stop_times WHERE trip_id IN " +
    "(SELECT DISTINCT trip_id FROM gtfs_trips WHERE route_id='" + routeId + "');";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Stop IDs to get
    let stopIds = [];

    // Parse each row of the results
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];
      stopIds.push(row.stop_id);
    }

    // Build stops from the stop ids
    getStop(db, stopIds, function(err, stops) {

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

      // Return sorted stops with callback
      if ( stops !== undefined ) {
        stops.sort(Stop.sortByName);
      }
      return callback(null, stops);

    });

  });

}

/**
 * Get all of the Stops with a valid (!= -1) Status ID that are stored
 * in the passed database (sorted alphabetically)
 *
 * **package:** core.query.stops.getStopsWithStatus()
 *
 * @param {RightTrackDB} db The Right Track Database to query
 * @param {getStopsCallback} callback getStops callback function
 */
function getStopsWithStatus(db, callback) {

  // Build select statement
  let select = "SELECT gtfs_stops.stop_id, gtfs_stops.stop_name, gtfs_stops.stop_lat, " +
    "gtfs_stops.stop_lon, gtfs_stops.stop_url, gtfs_stops.wheelchair_boarding, " +
    "rt_stops_extra.status_id, rt_stops_extra.display_name, rt_stops_extra.transfer_weight " +
    "FROM gtfs_stops, rt_stops_extra " +
    "WHERE gtfs_stops.stop_id=rt_stops_extra.stop_id AND " +
    "rt_stops_extra.status_id <> '-1';";

  // Query the database
  db.select(select, function(err, results) {

    // Database query error
    if ( err ) {
      return callback(err);
    }

    // Array to hold stops to return
    let stops = [];

    // Parse each row
    for ( let i = 0; i < results.length; i++ ) {
      let row = results[i];

      // Use rt display_name if provided
      let final_name = row.stop_name;
      if ( row.display_name !== undefined && row.display_name !== "" ) {
        final_name = row.display_name;
      }

      // Set wheelchair boarding to unknown if status not in database
      let wheelchair_boarding = row.wheelchair_boarding;
      if ( row.wheelchair_boarding === null ) {
        wheelchair_boarding = Stop.WHEELCHAIR_BOARDING_UNKNOWN;
      }

      // build the Stop
      let stop = new Stop(
        row.stop_id,
        final_name,
        row.stop_lat,
        row.stop_lon,
        row.stop_url,
        wheelchair_boarding,
        row.status_id,
        row.transfer_weight
      );

      // Add stop to return array
      stops.push(stop);

    }

    // return the stops in the callback
    stops.sort(Stop.sortByName);
    return callback(null, stops);

  });

}

// TODO: Get Stops near location
// TODO: Get closest stop to location

// Export Functions
module.exports = {
  getStop: getStop,
  getStopByName: getStopByName,
  getStopByStatusId: getStopByStatusId,
  getStops: getStops,
  getStopsByRoute: getStopsByRoute,
  getStopsWithStatus: getStopsWithStatus
};<|endoftext|>