
/*!
 * socket.io-node
 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Test dependencies.
 */

var sio = require('../')
  , should = require('./common')
  , parser = sio.parser
  , ports = 15800;

/**
 * Tests.
 */

module.exports = {
  'websocket identifies as websocket': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.set('transports', ['websocket']);
    io.on('connection', function (socket) {
      socket.manager.transports[socket.id].name.should.equal('websocket');
      ws.finishClose();
      cl.end();
      io.server.close();
      done();
    });
    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
    });
  },

  'default websocket draft parser is used for unknown sec-websocket-version': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.set('transports', ['websocket']);
    io.sockets.on('connection', function (socket) {
      socket.manager.transports[socket.id].protocolVersion.should.equal('hixie-76');
      ws.finishClose();
      cl.end();
      io.server.close();
      done();
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
    });
  },

  'hybi-07-12 websocket draft parser is used for sec-websocket-version: 8': function (done) {
    var cl = client(++ports)
      , io = create(cl);

    io.set('transports', ['websocket']);
    io.sockets.on('connection', function (socket) {
      socket.manager.transports[socket.id].protocolVersion.should.equal('07-12');
      cl.end();
      io.server.close();
      done();
    });

    var headers = {
      'sec-websocket-version': 8,
      'upgrade': 'websocket',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    cl.get('/socket.io/{protocol}', {}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      cl.get(url, {headers: headers}, function (res, data) {});
    });
  },

  'hybi-16 websocket draft parser is used for sec-websocket-version: 13': function (done) {
    var cl = client(++ports)
      , io = create(cl)

    io.set('transports', ['websocket']);

    io.sockets.on('connection', function (socket) {
      socket.manager.transports[socket.id].protocolVersion.should.equal('16');
      cl.end();
      io.server.close();
      done();
    });

    var headers = {
      'sec-websocket-version': 13,
      'upgrade': 'websocket',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    cl.get('/socket.io/{protocol}', {}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      cl.get(url, {headers: headers}, function (res, data) {});
    });
  },

  'hybi-07-12 origin filter blocks access for mismatched sec-websocket-origin': function (done) {
    var cl = client(++ports)
      , io = create(cl)

    io.set('transports', ['websocket']);
    io.set('origins', 'foo.bar.com:*');
    var notConnected = true;
    io.sockets.on('connection', function() {
        notConnected = false;
    });

    var headers = {
      'sec-websocket-version': 8,
      'upgrade': 'websocket',
      'Sec-WebSocket-Origin': 'http://baz.bar.com',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    // handshake uses correct origin -- we want to block the actual websocket call
    cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      var req = cl.get(url, {headers: headers}, function (res, data) {});
      var closed = false;
      (req.socket || req).on('close', function() {
        if (closed) return;
        closed = true;
        notConnected.should.be.true;
        cl.end();
        try {
          io.server.close();
        }
        catch (e) {}
        done();
      });
    });
  },

  'hybi-16 origin filter blocks access for mismatched sec-websocket-origin': function (done) {
    var cl = client(++ports)
      , io = create(cl);

    io.set('transports', ['websocket']);
    io.set('origins', 'foo.bar.com:*');
    var notConnected = true;
    io.sockets.on('connection', function() {
        notConnected = false;
    });

    var headers = {
      'sec-websocket-version': 13,
      'upgrade': 'websocket',
      'origin': 'http://baz.bar.com',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    // handshake uses correct origin -- we want to block the actual websocket call
    cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      var req = cl.get(url, {headers: headers}, function (res, data) {});
      var closed = false;
      (req.socket || req).on('close', function() {
        if (closed) return;
        closed = true;
        notConnected.should.be.true;
        cl.end();
        try {
          io.server.close();
        }
        catch (e) {}
        done();
      });
    });
  },

  'hybi-07-12 origin filter accepts implicit port 80 for sec-websocket-origin': function (done) {
done();return;
    var cl = client(++ports)
      , io = create(cl)

    io.set('transports', ['websocket']);
    io.set('origins', 'foo.bar.com:80');

    var headers = {
      'sec-websocket-version': 8,
      'upgrade': 'websocket',
      'Sec-WebSocket-Origin': 'http://foo.bar.com',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    io.sockets.on('connection', function() {
        cl.end();
        io.server.close();
        done();
    });

    // handshake uses correct origin -- we want to block the actual websocket call
    cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      cl.get(url, {headers: headers}, function (res, data) {});
    });
  },

  'hybi-16 origin filter accepts implicit port 80 for sec-websocket-origin': function (done) {
    var cl = client(++ports)
      , io = create(cl)

    io.set('transports', ['websocket']);
    io.set('origins', 'foo.bar.com:80');

    var headers = {
      'sec-websocket-version': 13,
      'upgrade': 'websocket',
      'origin': 'http://foo.bar.com',
      'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
    }

    io.sockets.on('connection', function() {
        cl.end();
        io.server.close();
        done();
    });

    // handshake uses correct origin -- we want to block the actual websocket call
    cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
      var sid = data.split(':')[0];
      var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
      cl.get(url, {headers: headers}, function (res, data) {});
    });
  },

  'test that not responding to a heartbeat drops client': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , messages = 0
      , ws;

    io.configure(function () {
      io.set('heartbeat interval', .05);
      io.set('heartbeat timeout', .05);
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      socket.on('disconnect', function (reason) {
        beat.should.be.true;
        reason.should.eql('heartbeat timeout');

        cl.end();
        ws.finishClose();
        io.server.close();
        done();
      });
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (packet) {
        if (++messages == 1) {
          packet.type.should.eql('connect');
        } else {
          packet.type.should.eql('heartbeat');
          beat = true;
        }
      });
    });
  },

  'test that responding to a heartbeat maintains session': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , messages = 0
      , heartbeats = 0
      , ws;

    io.configure(function () {
      io.set('heartbeat interval', .05);
      io.set('heartbeat timeout', .05);
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      socket.on('disconnect', function (reason) {
        heartbeats.should.eql(2);
        reason.should.eql('heartbeat timeout');

        cl.end();
        ws.finishClose();
        io.server.close();
        done();
      });
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (packet) {
        if (++messages == 1) {
          packet.type.should.eql('connect');
        } else {
          packet.type.should.eql('heartbeat');
          heartbeats++;

          if (heartbeats == 1) {
            ws.packet({ type: 'heartbeat' });
          }
        }
      });
    });
  },

  'test sending deliverable volatile messages': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , messages = 0
      , messaged = false;

    io.configure(function () {
      io.set('close timeout', .05);
    });

    io.sockets.on('connection', function (socket) {
      socket.volatile.send('tobi');

      socket.on('disconnect', function () {
        messaged.should.be.true;
        cl.end();
        io.server.close();
        done();
      });
    });

    cl.handshake(function (sid) {
      var ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (++messages == 1) {
          msg.type.should.eql('connect');
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'tobi'
            , endpoint: ''
          });
          messaged = true;
          ws.finishClose();
        }
      });
    });
  },

  'test sending deliverable volatile json': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , messaged = false;

    io.configure(function () {
      io.set('close timeout', .05);
    });

    io.sockets.on('connection', function (socket) {
      socket.volatile.json.send([1, 2, 3]);

      socket.on('disconnect', function () {
        messaged.should.be.true;
        cl.end();
        io.server.close();
        done();
      });
    });

    cl.handshake(function (sid) {
      var ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: [1, 2, 3]
            , endpoint: ''
          });
          messaged = true;
          ws.finishClose();
        }
      });
    });
  },

  'test sending deliverable volatile events': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , messaged = false;

    io.configure(function () {
      io.set('close timeout', .05);
    });

    io.sockets.on('connection', function (socket) {
      socket.volatile.emit('tobi');

      socket.on('disconnect', function () {
        messaged.should.be.true;
        cl.end();
        io.server.close();
        done();
      });
    });

    cl.handshake(function (sid) {
      var ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'tobi'
            , endpoint: ''
            , args: []
          });
          messaged = true;
          ws.finishClose();
        }
      });
    });
  },

  'test sending to all clients in a namespace': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 2) {
        io.sockets.send('yup');
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 2) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'yup'
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'yup'
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });
  },

  'test sending json to all clients in a namespace': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 2) {
        io.sockets.json.send({ a: 'b' });
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 2) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: { a: 'b' }
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: { a: 'b' }
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });
  },

  'test emitting to all clients in a namespace': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 2) {
        io.sockets.emit('tobi', 'rapture');
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 2) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'tobi'
            , args: ['rapture']
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'tobi'
            , args: ['rapture']
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });
  },

  'test sending to all clients in a room': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , joins = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections != 3) {
        socket.join('woot');
        joins++;

        if (joins == 2) {
          setTimeout(function () {
            connections.should.eql(3);
            io.sockets.in('woot').send('hahaha');
          }, 20);
        }
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'hahaha'
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws1.finishClose();
      }, 50);
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'hahaha'
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws2.finishClose();
      }, 50);
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'hahaha'
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws3.finishClose();
      }, 50);
    });
  },

  'test sending json to all clients in a room': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , joins = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections != 3) {
        socket.join('woot');
        joins++;

        if (joins == 2) {
          setTimeout(function () {
            connections.should.eql(3);
            io.sockets.in('woot').json.send(123);
          }, 20);
        }
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: 123
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws1.finishClose();
      }, 50);
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: 123
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws2.finishClose();
      }, 50);
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: 123
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws3.finishClose();
      }, 50);
    });
  },

  'test emitting to all clients in a room': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , joins = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections != 3) {
        socket.join('woot');
        joins++;

        if (joins == 2) {
          setTimeout(function () {
            connections.should.eql(3);
            io.sockets.in('woot').emit('locki');
          }, 20);
        }
      }

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'locki'
            , args: []
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws1.finishClose();
      }, 50);
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'locki'
            , args: []
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws2.finishClose();
      }, 50);
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'locki'
            , args: []
            , endpoint: ''
          });

          messages++;
        }
      });

      setTimeout(function () {
        ws3.finishClose();
      }, 50);
    });
  },

  'test leaving a room': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , io = create(cl1)
      , joins = 0
      , disconnects = 0;

    io.set('close timeout', 0);

    io.sockets.on('connection', function (socket) {
      socket.join('foo');
      io.sockets.clients('foo').should.have.length(++joins);

      socket.on('disconnect', function () {
        socket.leave('foo');
        socket.leave('foo');
        socket.leave('foo');

        io.sockets.clients('foo').should.have.length(--joins);

        if (++disconnects == 2) {
          io.server.close();
          cl1.end();
          cl2.end();
          done();
        }
      })
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
           ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
          ws2.finishClose();
        }
      });
    });
  },

  'test message with broadcast flag': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      socket.on('trigger broadcast', function () {
        socket.broadcast.send('boom');
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'boom'
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'boom'
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('open', function () {
        ws3.packet({
            type: 'event'
          , name: 'trigger broadcast'
          , endpoint: ''
        });

        setTimeout(function () {
          ws3.finishClose();
        }, 50);
      });

      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          throw new Error('we shouldnt get a message here');
        }
      });
    });
  },

  'test json with broadcast flag': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      socket.on('trigger broadcast', function () {
        socket.broadcast.json.send([1, 2, 3]);
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: [1, 2, 3]
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: [1, 2, 3]
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('open', function () {
        ws3.packet({
            type: 'event'
          , name: 'trigger broadcast'
          , endpoint: ''
        });

        setTimeout(function () {
          ws3.finishClose();
        }, 50);
      });

      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          throw new Error('we shouldnt get a message here');
        }
      });
    });
  },

  'test event with broadcast flag': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      socket.on('trigger broadcast', function () {
        socket.broadcast.emit('hey', 'arnold');
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(2);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'hey'
            , args: ['arnold']
            , endpoint: ''
          });

          messages++;
          ws1.finishClose();
        }
      });
    });

    cl2.handshake(function (sid) {
      var ws2 = websocket(cl2, sid);
      ws2.on('message', function (msg) {
        if (!ws2.connected) {
          msg.type.should.eql('connect');
          ws2.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'hey'
            , args: ['arnold']
            , endpoint: ''
          });

          messages++;
          ws2.finishClose();
        }
      });
    });

    cl3.handshake(function (sid) {
      var ws3 = websocket(cl3, sid);
      ws3.on('open', function () {
        ws3.packet({
            type: 'event'
          , name: 'trigger broadcast'
          , endpoint: ''
        });

        setTimeout(function () {
          ws3.finishClose();
        }, 50);
      });

      ws3.on('message', function (msg) {
        if (!ws3.connected) {
          msg.type.should.eql('connect');
          ws3.connected = true;
        } else {
          throw new Error('we shouldnt get a message here');
        }
      });
    });
  },

  'test message with broadcast flag and to()': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 1) {
        socket.join('losers');
      }

      socket.on('trigger broadcast', function () {
        socket.broadcast.to('losers').send('boom');
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(1);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'message'
            , data: 'boom'
            , endpoint: ''
          });

          messages++;
        }
      });

      ws1.on('open', function () {
        cl2.handshake(function (sid) {
          var ws2 = websocket(cl2, sid);
          ws2.on('message', function (msg) {
            if (!ws2.connected) {
              msg.type.should.eql('connect');
              ws2.connected = true;
            } else {
              throw new Error('This socket shouldnt get a message');
            }
          });

          ws2.on('open', function () {
            cl3.handshake(function (sid) {
              var ws3 = websocket(cl3, sid);
              ws3.on('open', function () {
                ws3.packet({
                    type: 'event'
                  , name: 'trigger broadcast'
                  , endpoint: ''
                });

                setTimeout(function () {
                  ws1.finishClose();
                  ws2.finishClose();
                  ws3.finishClose();
                }, 50);
              });

              ws3.on('message', function (msg) {
                if (!ws3.connected) {
                  msg.type.should.eql('connect');
                  ws3.connected = true;
                } else {
                  throw new Error('we shouldnt get a message here');
                }
              });
            });
          });
        });
      });
    });
  },

  'test json with broadcast flag and to()': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 1) {
        socket.join('losers');
      }

      socket.on('trigger broadcast', function () {
        socket.broadcast.json.to('losers').send({ hello: 'world' });
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(1);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'json'
            , data: { hello: 'world' }
            , endpoint: ''
          });

          messages++;
        }
      });

      ws1.on('open', function () {
        cl2.handshake(function (sid) {
          var ws2 = websocket(cl2, sid);
          ws2.on('message', function (msg) {
            if (!ws2.connected) {
              msg.type.should.eql('connect');
              ws2.connected = true;
            } else {
              throw new Error('This socket shouldnt get a message');
            }
          });

          ws2.on('open', function () {
            cl3.handshake(function (sid) {
              var ws3 = websocket(cl3, sid);
              ws3.on('open', function () {
                ws3.packet({
                    type: 'event'
                  , name: 'trigger broadcast'
                  , endpoint: ''
                });

                setTimeout(function () {
                  ws1.finishClose();
                  ws2.finishClose();
                  ws3.finishClose();
                }, 50);
              });

              ws3.on('message', function (msg) {
                if (!ws3.connected) {
                  msg.type.should.eql('connect');
                  ws3.connected = true;
                } else {
                  throw new Error('we shouldnt get a message here');
                }
              });
            });
          });
        });
      });
    });
  },

  'test event with broadcast flag and to()': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , cl3 = client(port)
      , io = create(cl1)
      , messages = 0
      , connections = 0
      , disconnections = 0;

    io.configure(function () {
      io.set('close timeout', 0);
    });

    io.sockets.on('connection', function (socket) {
      connections++;

      if (connections == 1) {
        socket.join('losers');
      }

      socket.on('trigger broadcast', function () {
        socket.broadcast.to('losers').emit('victory');
      });

      socket.on('disconnect', function () {
        disconnections++;

        if (disconnections == 3) {
          messages.should.eql(1);
          cl1.end();
          cl2.end();
          cl3.end();
          io.server.close();
          done();
        }
      });
    });

    cl1.handshake(function (sid) {
      var ws1 = websocket(cl1, sid);
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
        } else {
          msg.should.eql({
              type: 'event'
            , name: 'victory'
            , args: []
            , endpoint: ''
          });

          messages++;
        }
      });

      ws1.on('open', function () {
        cl2.handshake(function (sid) {
          var ws2 = websocket(cl2, sid);
          ws2.on('message', function (msg) {
            if (!ws2.connected) {
              msg.type.should.eql('connect');
              ws2.connected = true;
            } else {
              throw new Error('This socket shouldnt get a message');
            };
          });

          ws2.on('open', function () {
            cl3.handshake(function (sid) {
              var ws3 = websocket(cl3, sid);
              ws3.on('open', function () {
                ws3.packet({
                    type: 'event'
                  , name: 'trigger broadcast'
                  , endpoint: ''
                });

                setTimeout(function () {
                  ws1.finishClose();
                  ws2.finishClose();
                  ws3.finishClose();
                }, 50);
              });

              ws3.on('message', function (msg) {
                if (!ws3.connected) {
                  msg.type.should.eql('connect');
                  ws3.connected = true;
                } else {
                  throw new Error('we shouldnt get a message here');
                }
              });
            });
          });
        });
      });
    });
  },

  'test accessing handshake data from sockets': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.sockets.on('connection', function (socket) {
      (!!socket.handshake.address.address).should.be.true;
      (!!socket.handshake.address.port).should.be.true;
      socket.handshake.headers.host.should.equal('localhost');
      socket.handshake.headers.connection.should.equal('keep-alive');
      socket.handshake.time.should.match(/GMT/);

      socket.on('disconnect', function () {
        setTimeout(function () {
          ws.finishClose();
          cl.end();
          io.server.close();
          done();
        }, 10);
      });

      socket.disconnect();
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        }
      });
    });
  },

  'test accessing the array of clients': function (done) {
    var port = ++ports
      , cl1 = client(port)
      , cl2 = client(port)
      , io = create(cl1)
      , total = 2
      , ws1, ws2;

    io.sockets.on('connection', function (socket) {
      socket.on('join ferrets', function () {
        socket.join('ferrets');
        socket.send('done');
      });
    });

    function check() {
      io.sockets.clients('ferrets').should.have.length(1);
      io.sockets.clients('ferrets')[0].should.be.an.instanceof(sio.Socket);
      io.sockets.clients('ferrets')[0].id.should.equal(ws1.sid);
      io.sockets.clients().should.have.length(2);
      io.sockets.clients()[0].should.be.an.instanceof(sio.Socket);
      io.sockets.clients()[0].id.should.equal(ws1.sid);
      io.sockets.clients()[1].should.be.an.instanceof(sio.Socket);
      io.sockets.clients()[1].id.should.equal(ws2.sid);

      ws1.finishClose();
      ws2.finishClose();
      cl1.end();
      cl2.end();
      io.server.close();
      done();
    };

    cl1.handshake(function (sid) {
      ws1 = websocket(cl1, sid);
      ws1.sid = sid;
      ws1.on('message', function (msg) {
        if (!ws1.connected) {
          msg.type.should.eql('connect');
          ws1.connected = true;
          ws1.packet({
              type: 'event'
            , name: 'join ferrets'
            , endpoint: ''
          });
        } else {
          cl2.handshake(function (sid) {
            ws2 = websocket(cl2, sid);
            ws2.sid = sid;
            ws2.on('message', function (msg) {
              if (!ws2.connected) {
                msg.type.should.eql('connect');
                ws2.connected = true;
                check();
              }
            });
          });
        }
      });
    });
  },

  'test accessing handshake data from sockets on disconnect': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.sockets.on('connection', function (socket) {
      socket.on('disconnect', function () {

      (!!socket.handshake.address.address).should.be.true;
      (!!socket.handshake.address.port).should.be.true;
      socket.handshake.headers.host.should.equal('localhost');
      socket.handshake.headers.connection.should.equal('keep-alive');
      socket.handshake.time.should.match(/GMT/);

        setTimeout(function () {
          ws.finishClose();
          cl.end();
          io.server.close();
          done();
        }, 10);
      });

      socket.disconnect();
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        }
      });
    });
  },

  'test for intentional and unintentional disconnects': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , calls = 0
      , ws;

    function close () {
      cl.end();
      io.server.close();
      ws.finishClose();
      done();
    }

    io.configure(function () {
      io.set('heartbeat interval', .05);
      io.set('heartbeat timeout', .05);
      io.set('close timeout', 0);
    });

    io.of('/foo').on('connection', function (socket) {
      socket.on('disconnect', function (reason) {
       reason.should.equal('packet');

       if (++calls == 2) close();
      });
    });

    io.of('/bar').on('connection', function (socket) {
      socket.on('disconnect', function (reason) {
        reason.should.equal('socket end');

        if (++calls == 2) close();
      });
    });

    cl.handshake(function (sid) {
      var messages = 0;
      ws = websocket(cl, sid);
      ws.on('open', function () {
        ws.packet({
            type: 'connect'
          , endpoint: '/foo'
        });
        ws.packet({
            type: 'connect'
          , endpoint: '/bar'
        });
      });

      ws.on('message', function (packet) {
        if (packet.type == 'connect') {
          if (++messages === 3) {
            ws.packet({ type: 'disconnect', endpoint:'/foo' });
            ws.finishClose();
          }
        }
      });
    });
  },

  'test socket clean up': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.sockets.on('connection', function (socket) {
      var self = this
        , id = socket.id;

      socket.on('disconnect', function () {
        setTimeout(function () {
          var available = !!self.sockets[id];

          available.should.be.false;
          ws.finishClose();
          cl.end();
          io.server.close();
          done();
        }, 10);
      });

      socket.disconnect();
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        }
      });
    });
  },

  'accessing the transport type': function (done) {
    var cl = client(++ports)
      , io = create(cl)
      , ws;

    io.sockets.on('connection', function (socket) {
      socket.transport.should.equal('websocket');

      socket.on('disconnect', function () {
        setTimeout(function () {
          ws.finishClose();
          cl.end();
          io.server.close();
          done();
        }, 10);
      });

      socket.disconnect();
    });

    cl.handshake(function (sid) {
      ws = websocket(cl, sid);
      ws.on('message', function (msg) {
        if (!ws.connected) {
          msg.type.should.eql('connect');
          ws.connected = true;
        }
      });
    });
  }

};
