From 06f551a8e8895b7df5c13a934482c159f3ee5db1 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Tue, 21 May 2013 10:30:43 +0100 Subject: [PATCH 01/16] First FSM tests. --- package.json | 6 ++++-- public/js/main.js | 3 ++- test/fixtures.js | 12 ++++++++++++ test/game_test.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/fixtures.js create mode 100644 test/game_test.js diff --git a/package.json b/package.json index 5467d5a..46c3e21 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ }, "main": "server.js", "scripts": { - "start": "server.js" + "start": "server.js", + "test": "tap ./test" }, "repository": { "type": "git", @@ -37,6 +38,7 @@ "async": "~0.1.22", "grunt-contrib-concat": "~0.1.2", "grunt-contrib-jshint": "~0.1.1", - "grunt-contrib-uglify": "~0.1.1" + "grunt-contrib-uglify": "~0.1.1", + "tap": "~0.4.3" } } diff --git a/public/js/main.js b/public/js/main.js index 15fbc29..9ec2f5d 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -18,6 +18,7 @@ var websocket = { event: 'receive_chat_msg' }; + var dataChannel = { send: function(message) { for (var connection in rtc.dataChannels) { @@ -169,7 +170,7 @@ $.on('pinchange', function () { rtc.connect((proto.indexOf('https') !== -1 ? 'wss:' : 'ws:') + href.substring(proto.length).split('#')[0], pin); }); -// should/could be done with bitwise state checking, +// should/could be done with bitwise state checking, // but it's late :( var state = { readyremote: false, diff --git a/test/fixtures.js b/test/fixtures.js new file mode 100644 index 0000000..e0a8f4d --- /dev/null +++ b/test/fixtures.js @@ -0,0 +1,12 @@ +module.exports = { + mock: { + req: { + session: {}, + params: [], + body: {} + }, + user: { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31' + } + } +}; \ No newline at end of file diff --git a/test/game_test.js b/test/game_test.js new file mode 100644 index 0000000..07c93c2 --- /dev/null +++ b/test/game_test.js @@ -0,0 +1,49 @@ +var games = require('../lib/game'), + t = require('tap'), + fix = require('./fixtures'); + +console.log(games); + +t.test('creating', function (t) { + + t.test('create with a fresh pin creates a game', function (t) { + var game = games.create(1111); + console.log(game); + t.ok(game, 'Returned game is truthy.'); + t.equal(game.pin, 1111, 'Returned pin is the same.'); + t.end(); + }); + + t.end(); +}); + +t.test('join', function (t) { + + t.test('joining non-existant game fails', function (t) { + var game = games.join(2222, fix.mock.req, {}); + t.notOk(game, 'Game is falsy.'); + t.end(); + }); + + t.test('joining a waiting game works', function (t) { + var createdGame = games.create(3333); + var game = games.join(3333, fix.mock.req, {}); + t.ok(game, 'Game is truthy.'); + t.equal(game.game.pin, 3333, 'Game pin matches.'); + t.equal(game.game, createdGame, 'Game matches.'); + t.end(); + }); + + t.test('2 people joining a waiting game transitions to ready', function (t) { + var game = games.create(4444); + var firstId = game.join(fix.mock.user); + t.equal(firstId, 'a', 'id for first user is a'); + var secondId = game.join(fix.mock.user); + t.equal(secondId, 'b', 'id for second user is b'); + t.equal(game.state, 'playing', 'State updated to playing.'); + t.end(); + }); + + t.end(); +}); + From 35940764f275022cc0925f2aae08c11c0744e511 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Tue, 21 May 2013 10:31:04 +0100 Subject: [PATCH 02/16] Clean up status route. --- lib/routes.js | 89 +++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index 4d06fa6..951554a 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -118,59 +118,56 @@ module.exports = function (app) { app.get('/status/:pin?', function (req, res, next) { var pin = req.params.pin, game = games.get(req.pin); - if (req.xhr) { - if (pin && game) { - var ready = function () { - var turn = game.isCurrentPlayer(req.session.playerId), - otherLetter = req.session.playerId === 'a' ? 'b' : 'a', - me = { - letter: req.session.playerId, - score: game.getPlayerByLetter(req.session.playerId).score - }, - them = { - letter: otherLetter, - score: game.getPlayerByLetter(otherLetter).score - }; - - res.send({ - type: 'ready', - data: { - towin: game.towin, - gameover: game.state === 'gameover', - me: me, - them: them, - currentPlayer: turn ? req.session.playerId : otherLetter, - turn: req.session.debug ? true : turn - } - }); - }; - - if (game.state === 'ready' || game.state === 'playing' || game.state === 'gameover') { - ready(); - } else if (game.state === 'waiting') { - console.log('waiting for game to start'); - - // FIXME this event isn't being picked up at all - game.on('state.ready', ready); - } - } else { - // start a game - pin = req.session.pin = games.getPin(req.session); - - game = games.get(pin); - var gamedata = games.join(pin, req, { ua: req.headers['user-agent'] }); + if (!req.xhr) return next(); + if (pin && game) { + var ready = function () { + var turn = game.isCurrentPlayer(req.session.playerId), + otherLetter = req.session.playerId === 'a' ? 'b' : 'a', + me = { + letter: req.session.playerId, + score: game.getPlayerByLetter(req.session.playerId).score + }, + them = { + letter: otherLetter, + score: game.getPlayerByLetter(otherLetter).score + }; - req.session.playerId = gamedata.playerId; - req.session.ingame = pin; res.send({ - type: 'start', + type: 'ready', data: { - pin: pin + towin: game.towin, + gameover: game.state === 'gameover', + me: me, + them: them, + currentPlayer: turn ? req.session.playerId : otherLetter, + turn: req.session.debug ? true : turn } }); + }; + + if (game.state === 'ready' || game.state === 'playing' || game.state === 'gameover') { + ready(); + } else if (game.state === 'waiting') { + console.log(pin, 'waiting for game to start'); + + // FIXME this event isn't being picked up at all + game.on('state.ready', ready); } } else { - next(); + // start a game + pin = req.session.pin = games.getPin(req.session); + + game = games.get(pin); + var gamedata = games.join(pin, req, { ua: req.headers['user-agent'] }); + + req.session.playerId = gamedata.playerId; + req.session.ingame = pin; + res.send({ + type: 'start', + data: { + pin: pin + } + }); } }); From c725114362d8734130616aebcf77fbbcf84f036e Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Tue, 21 May 2013 14:44:51 +0100 Subject: [PATCH 03/16] Tests and documentation. --- lib/routes.js | 45 +++++++++++++++++++++++++---- public/js/start.js | 71 +++++++++++++++++++++++++++++----------------- test/game_test.js | 46 +++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 32 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index 951554a..685982a 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -66,8 +66,6 @@ module.exports = function (app) { var gamedata = games.join(pin, { session: {} }, { ua: req.headers['user-agent'] }); - - console.log('-------------------'); console.log(gamedata.game.players); @@ -115,6 +113,27 @@ module.exports = function (app) { res.send(true); }); + /** + * Status: start a new game or hang an xhr until two players have joined. + * + * If there's no pin present (or no game for the given pin) then a new game is + * created by getPin, then retrieved by get(pin), and then the current user + * joins that new game. + * + * The pin is saves in req.session.pin and req.session.ingame, and the xhr + * is closed with a 'start' event. + * + * -- + * + * If there is a pin and matching game, it will do one of two things... + * + * If the game is in one of a few states (isn't the FSM supposed to manage + * that stuff?) then it must be ready to go, so close the xhr right away (via + * ready()), passing back various bits of data. + * + * If it's not yet ready, (ie, waiting), then we wait on the FSM changing + * state before closing the XHR. + */ app.get('/status/:pin?', function (req, res, next) { var pin = req.params.pin, game = games.get(req.pin); @@ -150,7 +169,6 @@ module.exports = function (app) { } else if (game.state === 'waiting') { console.log(pin, 'waiting for game to start'); - // FIXME this event isn't being picked up at all game.on('state.ready', ready); } } else { @@ -190,12 +208,17 @@ module.exports = function (app) { var result = false; if (req.session.ingame) { // player should leave this game first - var game = games.get(req.session.ingame); - game.leave(game.getOtherPlayer(req.session.playerId).id); + var game = games.get(req.session.ingame), + player = game.getOtherPlayer(req.session.playerId); + if (player) game.leave(player.id); } res.send(result); }); + /** + * Join a game. Hit when join form is submitted. This may create the game if + * it does not exist. + */ app.post('/join', function (req, res) { // TODO test if there's an active game under that pin, // if so, allow join and remove from cache @@ -224,6 +247,8 @@ module.exports = function (app) { res.redirect('/play'); } } else if (game) { + // I don't think this can happen, becuase in the case of a falsey game, a + // new one is created (above) console.log('failed joining - game may be full'); if (req.xhr) { res.send(false); @@ -240,6 +265,16 @@ module.exports = function (app) { } }); + /** + * All roads lead to the player view. + * + * Headshot is almost a single page app, but with server state and + * code injection. + * + * All scripts are injected simultanously and manage their own initialisation. + * Mostly it's in start.js where getState conspires with init to pick what to + * show to the user. + */ app.get(['/play', '/join', '/start', '/join/:pin?', '/start/:pin?'], function (req, res, next) { var debug = req.session.debug || false; diff --git a/public/js/start.js b/public/js/start.js index 6382127..2c3f5b5 100644 --- a/public/js/start.js +++ b/public/js/start.js @@ -17,43 +17,53 @@ turnEl.on('animationend', removeTurnClass); var timer = null, title = document.title; +/** + * Status opens an xhr to /status or /status/:pin. + * + * This will: + * - if there's no pin + * - create a game and re-request /status/:pin + * - otherwise + * - hang until another player joins and the xhr closes + * - then: + * - the URL is updated + * - user's score and turn bindings are set up + * - the game ('play' in game.js) is init'd + */ function status(callback) { + callback = callback || function () {}; clearTimeout(timer); xhr.get('/status/' + (pin ? pin : ''), function (err, result) { if (err) { console.log(this); timer = setTimeout(status, 5000); } - if (result) { - if (result.type === 'ready') { - window.history.replaceState({}, title, '/play'); - window.game = new Bind(result.data, { - 'me.score': '#myscore', - 'them.score': function (value) { - $.trigger('theirScore', value); - }, - 'turn': function (myturn) { - turnEl.dataset.turn = myturn; - $.trigger('myturn', myturn); - } - }); - towin = window.game.towin; - play.init(); - if (!ready) { - waiting[0].hidden = false; + if (!result) return callback(); + if (result.type === 'ready') { + window.history.replaceState({}, title, '/play'); + window.game = new Bind(result.data, { + 'me.score': '#myscore', + 'them.score': function (value) { + $.trigger('theirScore', value); + }, + 'turn': function (myturn) { + turnEl.dataset.turn = myturn; + $.trigger('myturn', myturn); } - - // this is done after play.init to ensure the event order is right - } else if (result.type === 'start') { - setPin(result.data.pin); - window.history.replaceState({}, title, '/start/' + pin); - status(); + }); + towin = window.game.towin; + play.init(); + if (!ready) { + waiting[0].hidden = false; } - } - if (callback) { - callback(); + // this is done after play.init to ensure the event order is right + } else if (result.type === 'start') { + setPin(result.data.pin); + window.history.replaceState({}, title, '/start/' + pin); + status(); } + callback(); }); } @@ -126,6 +136,12 @@ function tap(el, handler) { el.on('click', handler, false); } +/** + * Extract state and pin from the URL. + * + * `pin` is in the global scope, so this just modifies that, and returns the + * state, which is the first part of the URL. + */ function getState() { var l = window.location, state = 'join', @@ -157,6 +173,9 @@ function getState() { return state; } +/** + * Decide how load the app. Initialiased based on the state from getState. + */ function init(state) { console.log(state, pin); if (state === 'start' || (state === 'join' && pin)) { diff --git a/test/game_test.js b/test/game_test.js index 07c93c2..018bcb8 100644 --- a/test/game_test.js +++ b/test/game_test.js @@ -34,7 +34,16 @@ t.test('join', function (t) { t.end(); }); - t.test('2 people joining a waiting game transitions to ready', function (t) { + t.test('leaving a waiting game resets it', function (t) { + var game = games.create(5556); + var firstId = game.join(fix.mock.user); + t.equal(game.state, 'waiting', 'State updated to waiting.'); + game.leave(); + t.equal(game.players.length, 0, 'Players empty.'); + t.end(); + }); + + t.test('2 people joining a waiting game transitions to playing', function (t) { var game = games.create(4444); var firstId = game.join(fix.mock.user); t.equal(firstId, 'a', 'id for first user is a'); @@ -44,6 +53,41 @@ t.test('join', function (t) { t.end(); }); + t.test('game can be played until gameover', function (t) { + var game = games.create(4445), + firstId = game.join(fix.mock.user), + secondId = game.join(fix.mock.user); + game.playturn({ playerId: 'a' }); + game.playturn({ playerId: 'b' }); + game.playturn({ playerId: 'a' }); + game.playturn({ playerId: 'b' }); + game.playturn({ playerId: 'a' }); + game.playturn({ playerId: 'b' }); + t.equal(game.state, 'gameover', 'State updated to gameover.'); + t.end(); + }); + + t.test('resetting transitions to waiting', function (t) { + var game = games.create(5555); + var firstId = game.join(fix.mock.user), + secondId = game.join(fix.mock.user); + t.equal(game.state, 'playing', 'State updated to playing.'); + game.reset(); + t.equal(game.state, 'waiting', 'State updated to waiting.'); + t.end(); + }); + + t.test('leaving a joined transitions to waiting', function (t) { + var game = games.create(6666); + var firstId = game.join(fix.mock.user), + secondId = game.join(fix.mock.user); + t.equal(game.state, 'playing', 'State updated to playing.'); + game.leave('a'); + t.equal(game.state, 'waiting', 'State updated to waiting.'); + t.equal(game.players.length, 1, 'One player left.'); + t.end(); + }); + t.end(); }); From dcfb85c8f5da676f5937b5d6def6899edac338f6 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Tue, 21 May 2013 16:53:55 +0100 Subject: [PATCH 04/16] Add some tests for /status. --- lib/routes.js | 4 +++- package.json | 4 +++- test/status_test.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test/status_test.js diff --git a/lib/routes.js b/lib/routes.js index 685982a..e8d53c4 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -137,7 +137,9 @@ module.exports = function (app) { app.get('/status/:pin?', function (req, res, next) { var pin = req.params.pin, game = games.get(req.pin); - if (!req.xhr) return next(); + console.log(pin, game); + // console.log('xhr?', req.xhr); + // if (!req.xhr) return next(); if (pin && game) { var ready = function () { var turn = game.isCurrentPlayer(req.session.playerId), diff --git a/package.json b/package.json index 46c3e21..a93e8e3 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "grunt-contrib-concat": "~0.1.2", "grunt-contrib-jshint": "~0.1.1", "grunt-contrib-uglify": "~0.1.1", - "tap": "~0.4.3" + "tap": "~0.4.3", + "request": "~2.21.0", + "xmlhttprequest": "~1.5.0" } } diff --git a/test/status_test.js b/test/status_test.js new file mode 100644 index 0000000..0b0e672 --- /dev/null +++ b/test/status_test.js @@ -0,0 +1,37 @@ +var t = require('tap'), + fix = require('./fixtures'), + games = require('../lib/game'), + request = require('request'), + base = 'http://localhost:8080'; + +var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; + +t.test('server', function (t) { + t.test('is started', function (t) { + request(base, function (err, res, body) { + if (err || !res) t.fail('Server not started. ' + err); + else t.pass('Server started.'); + t.end(); + }); + }); +}); + +t.test('/status', function (t) { + console.log('timesOut', t.timesOut); + t.test('creates a new game', function (t) { + request({ + url: base + '/status', + json: true + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'start', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.pin, 'Got a pin.'); + t.end(); + }); + }); +}); \ No newline at end of file From b9e2498f3bfd34c60330dec69837ea318ebd653a Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Tue, 21 May 2013 17:18:26 +0100 Subject: [PATCH 05/16] Document failing status test. --- test/status_test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/status_test.js b/test/status_test.js index 0b0e672..a1099d1 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -34,4 +34,20 @@ t.test('/status', function (t) { t.end(); }); }); + + // Currently fails, not sure why. Status creates a game, for some reason. + // t.test('hangs when pin is supplied the second player', function (t) { + // var game = games.create(4446); + // t.plan(1); + // request({ + // url: base + '/status/4446', + // json: true + // }, function (err, res, body) { + // console.log('err', err); + // console.log('body', body); + // t.fail('Status with pin returned'); + // t.end(); + // }); + // setTimeout(t.pass.bind(t, 'Timed out.'), 2000); + // }); }); \ No newline at end of file From 354b6e192b55d763982bc72312ece355e186726f Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 11:18:00 +0100 Subject: [PATCH 06/16] Testing hanging request to /status. --- test/status_test.js | 64 ++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index a1099d1..6c500e2 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -8,7 +8,10 @@ var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; t.test('server', function (t) { t.test('is started', function (t) { - request(base, function (err, res, body) { + request({ + url: base, + jar: request.jar() + }, function (err, res, body) { if (err || !res) t.fail('Server not started. ' + err); else t.pass('Server started.'); t.end(); @@ -16,12 +19,13 @@ t.test('server', function (t) { }); }); -t.test('/status', function (t) { - console.log('timesOut', t.timesOut); +t.test('status', function (t) { + t.test('creates a new game', function (t) { request({ url: base + '/status', - json: true + json: true, + jar: request.jar() }, function (err, res, body) { if (err || !res) { t.fail('Error. ' + err); @@ -36,18 +40,42 @@ t.test('/status', function (t) { }); // Currently fails, not sure why. Status creates a game, for some reason. - // t.test('hangs when pin is supplied the second player', function (t) { - // var game = games.create(4446); - // t.plan(1); - // request({ - // url: base + '/status/4446', - // json: true - // }, function (err, res, body) { - // console.log('err', err); - // console.log('body', body); - // t.fail('Status with pin returned'); - // t.end(); - // }); - // setTimeout(t.pass.bind(t, 'Timed out.'), 2000); - // }); + t.test('hangs when pin is supplied', function (t) { + var hangingRequest, + cancelled = false, + jar = request.jar(); + request({ + url: base + '/status', + json: true, + jar: jar + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + body.data.pin, + json: true, + jar: jar + }, function (err, res, body) { + // If the connection is manually reset, don't error. + if (cancelled) return; + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + t.fail('Status with pin returned'); + t.end(); + }); + }); + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + t.end(); + }, 2000); + }); + }); \ No newline at end of file From b72be3bcf04e3f3fec33f217a7595f21e6dd1efc Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 13:28:07 +0100 Subject: [PATCH 07/16] 2 players join status test. --- lib/routes.js | 28 ++++++------- test/status_test.js | 95 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index e8d53c4..3fab0c7 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -244,26 +244,20 @@ module.exports = function (app) { req.session.ingame = pin; if (req.xhr) { - res.send(true); - } else { - res.redirect('/play'); - } - } else if (game) { - // I don't think this can happen, becuase in the case of a falsey game, a - // new one is created (above) - console.log('failed joining - game may be full'); - if (req.xhr) { - res.send(false); - } else { - res.render('join', { - 'message': "That game is full!" - }); + return res.send(true); } - } else if (req.xhr) { - console.log('no game data'); + return res.redirect('/play'); + } + + // I don't think this can happen, becuase in the case of a falsey game, a + // new one is created (above) + console.log('failed joining - game may be full'); + if (req.xhr) { res.send(false); } else { - res.redirect('/join/' + pin); + res.render('join', { + 'message': "That game is full!" + }); } }); diff --git a/test/status_test.js b/test/status_test.js index 6c500e2..3bf4b4c 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -1,6 +1,7 @@ var t = require('tap'), fix = require('./fixtures'), games = require('../lib/game'), + async = require('async'), request = require('request'), base = 'http://localhost:8080'; @@ -78,4 +79,98 @@ t.test('status', function (t) { }, 2000); }); + // Currently fails, not sure why. Status creates a game, for some reason. + t.test('game starts when 2 players join', function (t) { + var hangingRequest, + cancelled = false, + jar = request.jar(); + request({ + url: base + '/status', + json: true, + jar: jar + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + + var pin = body.data.pin; + + async.parallel({ + one: function (done) { + + /** + * This simulates player who clicked 'New Game' + */ + setTimeout(function () { + + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); + + }, 50); + + }, + two: function (done) { + + // This simulates player who clicked 'Join Game' and entered `pin` + setTimeout(function () { + + var newJar = request.jar(); + request.post({ + url: base + '/join', + body: { pin: pin }, + json: true, + jar: newJar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 2 join:', body); + if (err || !body) { return done('Failed to join'); } + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: newJar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 2 status:', body); + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); + + }, 500); + + } + }, function (err, players) { + if (err) return t.fail(err); + t.ok(players.one.me.letter !== players.two.me.letter, + 'Players are serverd different letters'); + t.equal(players.one.currentPlayer, players.two.currentPlayer, + 'Both are sent the same current player.'); + t.end(); + }); + + }); + }); + }); \ No newline at end of file From 9673b80b0981c9fd751a27b62d271feae8cf0274 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 13:35:15 +0100 Subject: [PATCH 08/16] Unnecessary comment. --- test/status_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/status_test.js b/test/status_test.js index 3bf4b4c..fc9a690 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -79,7 +79,6 @@ t.test('status', function (t) { }, 2000); }); - // Currently fails, not sure why. Status creates a game, for some reason. t.test('game starts when 2 players join', function (t) { var hangingRequest, cancelled = false, From ee0cfac81c9cd490ab81809f3ee246c8694f9e92 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 14:28:14 +0100 Subject: [PATCH 09/16] Player join, leave and another joins test. --- test/status_test.js | 186 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 5 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index fc9a690..e0156da 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -76,7 +76,7 @@ t.test('status', function (t) { hangingRequest.req.end(); hangingRequest.req.destroy(); t.end(); - }, 2000); + }, 1000); }); t.test('game starts when 2 players join', function (t) { @@ -126,12 +126,12 @@ t.test('status', function (t) { // This simulates player who clicked 'Join Game' and entered `pin` setTimeout(function () { - var newJar = request.jar(); + var player2Jar = request.jar(); request.post({ url: base + '/join', body: { pin: pin }, json: true, - jar: newJar, + jar: player2Jar, headers: { 'X-Requested-With': 'XMLHttpRequest' } @@ -142,7 +142,7 @@ t.test('status', function (t) { hangingRequest = request({ url: base + '/status/' + pin, json: true, - jar: newJar, + jar: player2Jar, headers: { 'X-Requested-With': 'XMLHttpRequest' } @@ -157,7 +157,7 @@ t.test('status', function (t) { }); }); - }, 500); + }, 300); } }, function (err, players) { @@ -172,4 +172,180 @@ t.test('status', function (t) { }); }); + t.test('game starts when 2 players join with very close timings', function (t) { + var hangingRequest, + cancelled = false, + jar = request.jar(); + request({ + url: base + '/status', + json: true, + jar: jar + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + + var pin = body.data.pin; + + async.parallel({ + one: function (done) { + + /** + * This simulates player who clicked 'New Game' + */ + setTimeout(function () { + + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); + + }, 10); + + }, + two: function (done) { + + // This simulates player who clicked 'Join Game' and entered `pin` + setTimeout(function () { + + var player2Jar = request.jar(); + request.post({ + url: base + '/join', + body: { pin: pin }, + json: true, + jar: player2Jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 2 join:', body); + if (err || !body) { return done('Failed to join'); } + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: player2Jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + console.log('player 2 status:', body); + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); + + }, 50); + + } + }, function (err, players) { + if (err) return t.fail(err); + t.ok(players.one.me.letter !== players.two.me.letter, + 'Players are serverd different letters'); + t.equal(players.one.currentPlayer, players.two.currentPlayer, + 'Both are sent the same current player.'); + t.end(); + }); + + }); + }); + + t.test('player joins then leaves, the another player joins', function (t) { + var cancelled = false, + jar = request.jar(); + request({ + url: base + '/status', + json: true, + jar: jar + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + + var pin = body.data.pin; + + // !! NOTE: use of series here. These fire in order. + async.series({ + one: function (done) { + /** + * This simulates player who clicked 'New Game' and then hits the + * homepage again, detroying their session and leaving the game + */ + request({ + url: base + '/', + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err) { + if (err || !body) { return done('Failed to leave'); } + done(); + }); + + }, + two: function (done) { + // This simulates player who clicked 'Join Game' and entered `pin` + // after previous player had left. + var player2Jar = request.jar(), + hangingRequest, + cancelled; + request.post({ + url: base + '/join', + body: { pin: pin }, + json: true, + jar: player2Jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: player2Jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + // If the connection is manually reset, don't error. + if (cancelled) return; + if (err || !body) { return done('Failed to join'); } + done('Status with pin returned'); + }); + }); + + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + done(); + }, 1000); + + } + }, function (err, players) { + if (err) return t.fail(err); + t.end(); + }); + + }); + }); + }); \ No newline at end of file From eff9210d6011ee2735a734e1ee9528197d48f4e2 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:00:08 +0100 Subject: [PATCH 10/16] Start, quit and rejoin test. --- test/status_test.js | 82 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/test/status_test.js b/test/status_test.js index e0156da..8930c5c 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -348,4 +348,86 @@ t.test('status', function (t) { }); }); + t.test('player joins then leaves, then joins with pin', function (t) { + var cancelled = false, + jar = request.jar(); + request({ + url: base + '/status', + json: true, + jar: jar + }, function (err, res, body) { + if (err || !res) { + t.fail('Error. ' + err); + return t.end(); + } + + var pin = body.data.pin; + + // !! NOTE: use of series here. These fire in order. + async.series({ + one: function (done) { + /** + * This simulates player who clicked 'New Game' and then hits the + * homepage again, detroying their session and leaving the game + */ + request({ + url: base + '/', + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err) { + if (err || !body) { return done('Failed to leave'); } + done(); + }); + + }, + two: function (done) { + // This simulates player who clicked 'Join Game' and entered `pin` + // after having left left. + var hangingRequest, + cancelled; + request.post({ + url: base + '/join', + body: { pin: pin }, + json: true, + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + // Hanging with pin + hangingRequest = request({ + url: base + '/status/' + pin, + json: true, + jar: jar, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }, function (err, res, body) { + // If the connection is manually reset, don't error. + if (cancelled) return; + if (err || !body) { return done('Failed to join'); } + done('Status with pin returned'); + }); + }); + + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + done(); + }, 1000); + + } + }, function (err, players) { + if (err) return t.fail(err); + t.end(); + }); + + }); + }); + }); \ No newline at end of file From b707e47674b8c725d3e1e299c6611739b47a8d1b Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:08:00 +0100 Subject: [PATCH 11/16] Remove unneeded comment. --- test/status_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/status_test.js b/test/status_test.js index 8930c5c..8dcf351 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -40,7 +40,6 @@ t.test('status', function (t) { }); }); - // Currently fails, not sure why. Status creates a game, for some reason. t.test('hangs when pin is supplied', function (t) { var hangingRequest, cancelled = false, From 579a1b9fbf62084af4c0dabbf9461d88d29e490a Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:42:39 +0100 Subject: [PATCH 12/16] Use user fixture to make talking to the server easier. --- test/fixtures.js | 66 +++++++++++++++++++- test/status_test.js | 146 ++++++++++++++++---------------------------- 2 files changed, 115 insertions(+), 97 deletions(-) diff --git a/test/fixtures.js b/test/fixtures.js index e0a8f4d..f46b769 100644 --- a/test/fixtures.js +++ b/test/fixtures.js @@ -1,4 +1,7 @@ -module.exports = { +var request = require('request'), + _ = require('underscore'); + +var fix = { mock: { req: { session: {}, @@ -9,4 +12,63 @@ module.exports = { ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31' } } -}; \ No newline at end of file +}; + +/** + * Useful user object for testing endpoints. + * + * Create like so: + * var user = Object.create(fix.user).init(); + */ +fix.user = { + + /** + * Setup up user object + */ + init: function (opts) { + this.jar = request.jar(); + _.extend(this, opts); + return this; + }, + + /** + * Visit a given URL, passing options directly to request. + * + * user.visit('/fish', function (err, res, body) { . . . }); + * user.visit('/fish', { json: true }, function (err, res, body) { . . . }); + */ + visit: function (url, opts, cb) { + if (typeof opts === 'function') { cb = opts; opts = {}; } + var options = _.defaults({}, opts, { + url: url, + jar: this.jar + });; + return request(options, cb); + }, + + /** + * Simulate an XHR to the given URL. Assumes JSON will be returned, but that + * can be overridden in the options. + * + * user.xhr('/status', function (err, res, body) { . . . }); + * user.xhr('/text', { json: false }, function (err, res, body) { . . . }); + */ + xhr: function (url, opts, cb) { + if (typeof opts === 'function') { cb = opts; opts = {}; } + if (typeof opts.json === "undefined") opts.json = true; + var options = _.defaults({}, opts, { + json: true, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + return this.visit.call(this, url, options, cb); + } + +}; + +fix.createNewUser = function (options) { + return Object.create(fix.user).init(options); +} + +module.exports = fix; \ No newline at end of file diff --git a/test/status_test.js b/test/status_test.js index 8dcf351..671c4a9 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -23,15 +23,9 @@ t.test('server', function (t) { t.test('status', function (t) { t.test('creates a new game', function (t) { - request({ - url: base + '/status', - json: true, - jar: request.jar() - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } +var user = fix.createNewUser(); + user.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } t.ok(body, 'Got a body.'); t.equal(body.type, 'start', 'Got a body.'); t.ok(body.data, 'Got some data.'); @@ -42,33 +36,23 @@ t.test('status', function (t) { t.test('hangs when pin is supplied', function (t) { var hangingRequest, - cancelled = false, - jar = request.jar(); - request({ - url: base + '/status', - json: true, - jar: jar - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + body.data.pin, - json: true, - jar: jar - }, function (err, res, body) { - // If the connection is manually reset, don't error. - if (cancelled) return; - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } - t.fail('Status with pin returned'); - t.end(); - }); - }); + cancelled = false; + var user = fix.createNewUser(); + user + .xhr(base + '/status', + function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + // Hanging with pin + hangingRequest = user + .xhr(base + '/status/' + body.data.pin, + function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); + }); setTimeout(function () { t.pass('Timed out.'); cancelled = true; @@ -80,17 +64,11 @@ t.test('status', function (t) { t.test('game starts when 2 players join', function (t) { var hangingRequest, - cancelled = false, - jar = request.jar(); - request({ - url: base + '/status', - json: true, - jar: jar - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } + cancelled = false; + var player1 = fix.createNewUser(), + player2 = fix.createNewUser(); + player1.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } var pin = body.data.pin; @@ -100,24 +78,14 @@ t.test('status', function (t) { /** * This simulates player who clicked 'New Game' */ - setTimeout(function () { - - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 1 status:', body); - if (err || !body) { return done('Failed to join'); } - t.pass('IT GOT HERE!'); - done(null, body.data); - }); - - }, 50); + player1 + .xhr(base + '/status/' + pin, + function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); }, two: function (done) { @@ -125,36 +93,24 @@ t.test('status', function (t) { // This simulates player who clicked 'Join Game' and entered `pin` setTimeout(function () { - var player2Jar = request.jar(); - request.post({ - url: base + '/join', - body: { pin: pin }, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 2 join:', body); - if (err || !body) { return done('Failed to join'); } - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 2 status:', body); - if (err || !body) { return done('Failed to get status.'); } - t.ok(body, 'Got a body.'); - t.equal(body.type, 'ready', 'Got a body.'); - t.ok(body.data, 'Got some data.'); - t.ok(body.data.towin, 'Got to windata.'); - done(null, body.data); - }); - }); + player2. + xhr(base + '/join', { + method: 'POST', + body: { pin: pin } + }, + function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + player2 + .xhr(base + '/status/' + pin, + function (err, res, body) { + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); }, 300); From 75465da6e4b45b5ba9b82884e2ff6964a678b528 Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:48:16 +0100 Subject: [PATCH 13/16] Upgrade speed test to user mock user. --- test/status_test.js | 90 ++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index 671c4a9..2d163e5 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -129,17 +129,11 @@ var user = fix.createNewUser(); t.test('game starts when 2 players join with very close timings', function (t) { var hangingRequest, - cancelled = false, - jar = request.jar(); - request({ - url: base + '/status', - json: true, - jar: jar - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } + cancelled = false; + var player1 = fix.createNewUser(), + player2 = fix.createNewUser(); + player1.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } var pin = body.data.pin; @@ -149,24 +143,14 @@ var user = fix.createNewUser(); /** * This simulates player who clicked 'New Game' */ - setTimeout(function () { - - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 1 status:', body); - if (err || !body) { return done('Failed to join'); } - t.pass('IT GOT HERE!'); - done(null, body.data); - }); - - }, 10); + player1 + .xhr(base + '/status/' + pin, + function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); }, two: function (done) { @@ -174,36 +158,24 @@ var user = fix.createNewUser(); // This simulates player who clicked 'Join Game' and entered `pin` setTimeout(function () { - var player2Jar = request.jar(); - request.post({ - url: base + '/join', - body: { pin: pin }, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 2 join:', body); - if (err || !body) { return done('Failed to join'); } - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - console.log('player 2 status:', body); - if (err || !body) { return done('Failed to get status.'); } - t.ok(body, 'Got a body.'); - t.equal(body.type, 'ready', 'Got a body.'); - t.ok(body.data, 'Got some data.'); - t.ok(body.data.towin, 'Got to windata.'); - done(null, body.data); - }); - }); + player2. + xhr(base + '/join', { + method: 'POST', + body: { pin: pin } + }, + function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + player2 + .xhr(base + '/status/' + pin, + function (err, res, body) { + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); }, 50); From d1e5792b47175f26c1d7a95e691640ff2e9142ba Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:53:11 +0100 Subject: [PATCH 14/16] Upgrade join, leave, another joins test. --- test/status_test.js | 109 +++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 66 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index 2d163e5..2a9b036 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -38,21 +38,19 @@ var user = fix.createNewUser(); var hangingRequest, cancelled = false; var user = fix.createNewUser(); - user - .xhr(base + '/status', - function (err, res, body) { - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - // Hanging with pin - hangingRequest = user - .xhr(base + '/status/' + body.data.pin, - function (err, res, body) { - // If the connection is manually killed, don't error. - if (cancelled) return; - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - t.fail('Status with pin returned'); - t.end(); - }); - }); + user.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + // Hanging with pin + hangingRequest = user + .xhr(base + '/status/' + body.data.pin, + function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); + }); setTimeout(function () { t.pass('Timed out.'); cancelled = true; @@ -193,34 +191,23 @@ var user = fix.createNewUser(); }); t.test('player joins then leaves, the another player joins', function (t) { - var cancelled = false, - jar = request.jar(); - request({ - url: base + '/status', - json: true, - jar: jar - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } + var cancelled = false; + var player1 = fix.createNewUser(), + player2 = fix.createNewUser(); + player1.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } var pin = body.data.pin; // !! NOTE: use of series here. These fire in order. async.series({ one: function (done) { + /** * This simulates player who clicked 'New Game' and then hits the * homepage again, detroying their session and leaving the game */ - request({ - url: base + '/', - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err) { + player1.xhr(base + '/', function (err) { if (err || !body) { return done('Failed to leave'); } done(); }); @@ -229,43 +216,33 @@ var user = fix.createNewUser(); two: function (done) { // This simulates player who clicked 'Join Game' and entered `pin` // after previous player had left. - var player2Jar = request.jar(), - hangingRequest, - cancelled; - request.post({ - url: base + '/join', - body: { pin: pin }, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { + var hangingRequest, cancelled; + + player2.xhr(base + '/join', { + method: 'POST', + body: { pin: pin } + }, + function (err, res, body) { if (err || !body) { return done('Failed to join'); } // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: player2Jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - // If the connection is manually reset, don't error. - if (cancelled) return; - if (err || !body) { return done('Failed to join'); } - done('Status with pin returned'); - }); - }); + hangingRequest = player2 + .xhr(base + '/status/' + pin, function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); - setTimeout(function () { - t.pass('Timed out.'); - cancelled = true; - hangingRequest.req.end(); - hangingRequest.req.destroy(); - done(); - }, 1000); + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + done(); + }, 1000); + }); } }, function (err, players) { if (err) return t.fail(err); From 51b43581a16ca405e38744fdf0067b0bd1dad24c Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:57:35 +0100 Subject: [PATCH 15/16] Cleanup formatting. --- test/status_test.js | 216 ++++++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index 2a9b036..a1ff791 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -23,34 +23,36 @@ t.test('server', function (t) { t.test('status', function (t) { t.test('creates a new game', function (t) { -var user = fix.createNewUser(); - user.xhr(base + '/status', function (err, res, body) { - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - t.ok(body, 'Got a body.'); - t.equal(body.type, 'start', 'Got a body.'); - t.ok(body.data, 'Got some data.'); - t.ok(body.data.pin, 'Got a pin.'); - t.end(); - }); + var user = fix.createNewUser(); + user.xhr(base + '/status', + function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'start', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.pin, 'Got a pin.'); + t.end(); + }); }); t.test('hangs when pin is supplied', function (t) { var hangingRequest, - cancelled = false; - var user = fix.createNewUser(); - user.xhr(base + '/status', function (err, res, body) { - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - // Hanging with pin - hangingRequest = user - .xhr(base + '/status/' + body.data.pin, - function (err, res, body) { - // If the connection is manually killed, don't error. - if (cancelled) return; - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - t.fail('Status with pin returned'); - t.end(); - }); - }); + cancelled = false, + user = fix.createNewUser(); + user.xhr(base + '/status', + function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + // Hanging with pin + hangingRequest = user + .xhr(base + '/status/' + body.data.pin, + function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); + }); setTimeout(function () { t.pass('Timed out.'); cancelled = true; @@ -62,8 +64,8 @@ var user = fix.createNewUser(); t.test('game starts when 2 players join', function (t) { var hangingRequest, - cancelled = false; - var player1 = fix.createNewUser(), + cancelled = false, + player1 = fix.createNewUser(), player2 = fix.createNewUser(); player1.xhr(base + '/status', function (err, res, body) { if (err || !res) { return t.fail('Error. ' + err) && t.end(); } @@ -76,39 +78,37 @@ var user = fix.createNewUser(); /** * This simulates player who clicked 'New Game' */ - player1 - .xhr(base + '/status/' + pin, - function (err, res, body) { - console.log('player 1 status:', body); - if (err || !body) { return done('Failed to join'); } - t.pass('IT GOT HERE!'); - done(null, body.data); - }); + player1.xhr(base + '/status/' + pin, + function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); }, two: function (done) { // This simulates player who clicked 'Join Game' and entered `pin` setTimeout(function () { - - player2. - xhr(base + '/join', { - method: 'POST', - body: { pin: pin } - }, - function (err, res, body) { - if (err || !body) { return done('Failed to join'); } - player2 - .xhr(base + '/status/' + pin, - function (err, res, body) { - if (err || !body) { return done('Failed to get status.'); } - t.ok(body, 'Got a body.'); - t.equal(body.type, 'ready', 'Got a body.'); - t.ok(body.data, 'Got some data.'); - t.ok(body.data.towin, 'Got to windata.'); - done(null, body.data); - }); - }); + var opts = { + method: 'POST', + body: { pin: pin } + }; + player2. xhr(base + '/join', opts, + function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + player2 + .xhr(base + '/status/' + pin, + function (err, res, body) { + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); }, 300); @@ -127,8 +127,8 @@ var user = fix.createNewUser(); t.test('game starts when 2 players join with very close timings', function (t) { var hangingRequest, - cancelled = false; - var player1 = fix.createNewUser(), + cancelled = false, + player1 = fix.createNewUser(), player2 = fix.createNewUser(); player1.xhr(base + '/status', function (err, res, body) { if (err || !res) { return t.fail('Error. ' + err) && t.end(); } @@ -141,39 +141,36 @@ var user = fix.createNewUser(); /** * This simulates player who clicked 'New Game' */ - player1 - .xhr(base + '/status/' + pin, - function (err, res, body) { - console.log('player 1 status:', body); - if (err || !body) { return done('Failed to join'); } - t.pass('IT GOT HERE!'); - done(null, body.data); - }); + player1.xhr(base + '/status/' + pin, + function (err, res, body) { + console.log('player 1 status:', body); + if (err || !body) { return done('Failed to join'); } + t.pass('IT GOT HERE!'); + done(null, body.data); + }); }, two: function (done) { // This simulates player who clicked 'Join Game' and entered `pin` setTimeout(function () { - - player2. - xhr(base + '/join', { - method: 'POST', - body: { pin: pin } - }, - function (err, res, body) { - if (err || !body) { return done('Failed to join'); } - player2 - .xhr(base + '/status/' + pin, - function (err, res, body) { - if (err || !body) { return done('Failed to get status.'); } - t.ok(body, 'Got a body.'); - t.equal(body.type, 'ready', 'Got a body.'); - t.ok(body.data, 'Got some data.'); - t.ok(body.data.towin, 'Got to windata.'); - done(null, body.data); - }); - }); + var opts = { + method: 'POST', + body: { pin: pin } + }; + player2.xhr(base + '/join', opts, + function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + player2.xhr(base + '/status/' + pin, + function (err, res, body) { + if (err || !body) { return done('Failed to get status.'); } + t.ok(body, 'Got a body.'); + t.equal(body.type, 'ready', 'Got a body.'); + t.ok(body.data, 'Got some data.'); + t.ok(body.data.towin, 'Got to windata.'); + done(null, body.data); + }); + }); }, 50); @@ -191,8 +188,8 @@ var user = fix.createNewUser(); }); t.test('player joins then leaves, the another player joins', function (t) { - var cancelled = false; - var player1 = fix.createNewUser(), + var cancelled = false, + player1 = fix.createNewUser(), player2 = fix.createNewUser(); player1.xhr(base + '/status', function (err, res, body) { if (err || !res) { return t.fail('Error. ' + err) && t.end(); } @@ -214,35 +211,38 @@ var user = fix.createNewUser(); }, two: function (done) { - // This simulates player who clicked 'Join Game' and entered `pin` - // after previous player had left. - var hangingRequest, cancelled; - player2.xhr(base + '/join', { + /** + * This simulates player who clicked 'Join Game' and entered `pin` + * after previous player had left. + */ + var hangingRequest, cancelled; + var opts = { method: 'POST', body: { pin: pin } - }, - function (err, res, body) { - if (err || !body) { return done('Failed to join'); } - // Hanging with pin - hangingRequest = player2 - .xhr(base + '/status/' + pin, function (err, res, body) { - // If the connection is manually killed, don't error. - if (cancelled) return; - if (err || !res) { return t.fail('Error. ' + err) && t.end(); } - t.fail('Status with pin returned'); - t.end(); - }); + }; + player2.xhr(base + '/join', opts, + function (err, res, body) { + if (err || !body) { return done('Failed to join'); } + // Hanging with pin + hangingRequest = player2 + .xhr(base + '/status/' + pin, function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); - setTimeout(function () { - t.pass('Timed out.'); - cancelled = true; - hangingRequest.req.end(); - hangingRequest.req.destroy(); - done(); - }, 1000); + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + done(); + }, 1000); - }); + }); } }, function (err, players) { if (err) return t.fail(err); From dab0c9b0fe04d224300dc700960d8e9e4c7b2afa Mon Sep 17 00:00:00 2001 From: Tom Ashworth Date: Wed, 22 May 2013 16:59:46 +0100 Subject: [PATCH 16/16] Upgrade join, leave then rejoin with pin test. --- test/status_test.js | 88 +++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/test/status_test.js b/test/status_test.js index a1ff791..3dfde22 100644 --- a/test/status_test.js +++ b/test/status_test.js @@ -254,76 +254,60 @@ t.test('status', function (t) { t.test('player joins then leaves, then joins with pin', function (t) { var cancelled = false, - jar = request.jar(); - request({ - url: base + '/status', - json: true, - jar: jar - }, function (err, res, body) { - if (err || !res) { - t.fail('Error. ' + err); - return t.end(); - } + player1 = fix.createNewUser(), + player2 = fix.createNewUser(); + player1.xhr(base + '/status', function (err, res, body) { + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } var pin = body.data.pin; // !! NOTE: use of series here. These fire in order. async.series({ one: function (done) { + /** * This simulates player who clicked 'New Game' and then hits the * homepage again, detroying their session and leaving the game */ - request({ - url: base + '/', - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err) { + player1.xhr(base + '/', function (err, res, body) { if (err || !body) { return done('Failed to leave'); } done(); }); }, two: function (done) { - // This simulates player who clicked 'Join Game' and entered `pin` - // after having left left. - var hangingRequest, - cancelled; - request.post({ - url: base + '/join', - body: { pin: pin }, - json: true, - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - if (err || !body) { return done('Failed to join'); } - // Hanging with pin - hangingRequest = request({ - url: base + '/status/' + pin, - json: true, - jar: jar, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, function (err, res, body) { - // If the connection is manually reset, don't error. - if (cancelled) return; + + /** + * This simulates player who clicked 'Join Game' and entered `pin` + * after previous player had left. + */ + var hangingRequest, cancelled; + var opts = { + method: 'POST', + body: { pin: pin } + }; + player2.xhr(base + '/join', opts, + function (err, res, body) { if (err || !body) { return done('Failed to join'); } - done('Status with pin returned'); - }); - }); + // Hanging with pin + hangingRequest = player2 + .xhr(base + '/status/' + pin, function (err, res, body) { + // If the connection is manually killed, don't error. + if (cancelled) return; + if (err || !res) { return t.fail('Error. ' + err) && t.end(); } + t.fail('Status with pin returned'); + t.end(); + }); - setTimeout(function () { - t.pass('Timed out.'); - cancelled = true; - hangingRequest.req.end(); - hangingRequest.req.destroy(); - done(); - }, 1000); + setTimeout(function () { + t.pass('Timed out.'); + cancelled = true; + hangingRequest.req.end(); + hangingRequest.req.destroy(); + done(); + }, 1000); + + }); } }, function (err, players) {