From 7a9d9021d47c3dbc18c441b29ffc95d96e65efeb Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 11:32:08 +0100 Subject: [PATCH 01/14] Upgrade test: allow comet messages through so upgrade handshake can complete --- spec/realtime/upgrade.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/realtime/upgrade.test.js b/spec/realtime/upgrade.test.js index e352b9d2ee..4d5a581a8f 100644 --- a/spec/realtime/upgrade.test.js +++ b/spec/realtime/upgrade.test.js @@ -177,9 +177,12 @@ define(['ably', 'shared_helper'], function(Ably, helper) { * so we can see if a message arrived. * NOTE: this relies on knowledge of the internal implementation * of the transport */ + + var originalOnChannelMessage = transport.onChannelMessage; transport.onChannelMessage = function(message) { if(message.messages) test.ok(false, 'Message received on comet transport'); + originalOnChannelMessage.apply(this, arguments); }; } }); From 7fedbafe55c244595df1efaf175743a02c0789a2 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 11:33:25 +0100 Subject: [PATCH 02/14] Spec helper for simulating dropped connections --- spec/browser/connection.test.js | 1 + spec/common/modules/shared_helper.js | 19 +++++++++++++++---- spec/realtime/failure.test.js | 3 ++- spec/realtime/resume.test.js | 21 ++++----------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/spec/browser/connection.test.js b/spec/browser/connection.test.js index f0ace8c10a..1b18ac4647 100644 --- a/spec/browser/connection.test.js +++ b/spec/browser/connection.test.js @@ -4,6 +4,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { var exports = {}, closeAndFinish = helper.closeAndFinish, monitorConnection = helper.monitorConnection, + simulateDroppedConnection = helper.simulateDroppedConnection, Defaults = Ably.Realtime.Defaults; function stopIfUnsupported(test) { diff --git a/spec/common/modules/shared_helper.js b/spec/common/modules/shared_helper.js index aa83262b9d..838f5949c1 100644 --- a/spec/common/modules/shared_helper.js +++ b/spec/common/modules/shared_helper.js @@ -44,6 +44,16 @@ define(['spec/common/modules/testapp_module', 'spec/common/modules/client_module callbackOnClose(realtime, function(){ test.done(); }) }; + var simulateDroppedConnection = function(realtime) { + // Go into the 'disconnected' state before actually disconnecting the transports + // to avoid the instantaneous reconnect attempt that would be triggered in + // notifyState by the active transport getting disconnected from a connected state + realtime.connection.once('disconnected', function(){ + realtime.connection.connectionManager.disconnectAllTransports(); + }); + realtime.connection.connectionManager.requestState({state: 'disconnected'}); + } + function callbackOnClose(realtime, callback) { if(realtime.connection.connectionManager.activeProtocol === null) { console.log("No transport established; closing connection and calling test.done()") @@ -110,9 +120,10 @@ define(['spec/common/modules/testapp_module', 'spec/common/modules/client_module loadTestData: testAppManager.loadJsonData, - displayError: displayError, - monitorConnection: monitorConnection, - closeAndFinish: closeAndFinish, - withTimeout: withTimeout + displayError: displayError, + monitorConnection: monitorConnection, + closeAndFinish: closeAndFinish, + simulateDroppedConnection: simulateDroppedConnection, + withTimeout: withTimeout }; }); diff --git a/spec/realtime/failure.test.js b/spec/realtime/failure.test.js index 9535c0f29c..1bcfabf566 100644 --- a/spec/realtime/failure.test.js +++ b/spec/realtime/failure.test.js @@ -4,6 +4,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { var exports = {}, closeAndFinish = helper.closeAndFinish, monitorConnection = helper.monitorConnection, + simulateDroppedConnection = helper.simulateDroppedConnection, // Ably.Realtime.ConnectionManager not defined in node availableTransports = typeof Ably.Realtime.ConnectionManager === 'undefined' ? Ably.Realtime.Defaults.transports : Object.keys(Ably.Realtime.ConnectionManager.transports); @@ -72,7 +73,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { test.ok(false, 'connection state for transports ' + transports + ' should be disconnected, not failed'); cb(null, realtime); }); - realtime.connection.connectionManager.activeProtocol.transport.disconnect(); + simulateDroppedConnection(realtime); }); }; }; diff --git a/spec/realtime/resume.test.js b/spec/realtime/resume.test.js index a47d66bfa8..b78428db42 100644 --- a/spec/realtime/resume.test.js +++ b/spec/realtime/resume.test.js @@ -3,7 +3,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { var exports = {}, closeAndFinish = helper.closeAndFinish, - monitorConnection = helper.monitorConnection; + monitorConnection = helper.monitorConnection, + simulateDroppedConnection = helper.simulateDroppedConnection; exports.setupResume = function(test) { test.expect(1); @@ -42,11 +43,6 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { var txChannel = txRealtime.channels.get(channelName); var rxCount = 0; - var lastActiveRxTransport; - rxRealtime.connection.connectionManager.on('transport.active', function(transport) { - lastActiveRxTransport = transport; - }); - function phase0(callback) { attachChannels([rxChannel, txChannel], callback); } @@ -75,11 +71,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { } function phase2(callback) { - /* disconnect the transport - * NOTE: this uses knowledge of the internal operation - * of the client library to simulate a dropped connection - * without explicitly closing the connection */ - lastActiveRxTransport.disconnect(); + simulateDroppedConnection(rxRealtime); /* continue in 5 seconds */ setTimeout(callback, 5000); } @@ -195,11 +187,6 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { var txChannel = txRealtime.channels.get('resume1'); var rxCount = 0; - var lastActiveRxTransport; - rxRealtime.connection.connectionManager.on('transport.active', function(transport) { - lastActiveRxTransport = transport; - }); - function phase0(callback) { attachChannels([rxChannel, txChannel], callback); } @@ -231,7 +218,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { * NOTE: this uses knowledge of the internal operation * of the client library to simulate a dropped connection * without explicitly closing the connection */ - lastActiveRxTransport.disconnect(); + simulateDroppedConnection(rxRealtime); var txCount = 0; function ph2TxOnce() { From bcdf532610d4d46528d5977805a036bbe164d10b Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 20:02:43 +0100 Subject: [PATCH 03/14] Make comet transports emit actual ProtocolMessages not just plain objects --- common/lib/transport/comettransport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/transport/comettransport.js b/common/lib/transport/comettransport.js index ee8d124f2e..27bb7b1160 100644 --- a/common/lib/transport/comettransport.js +++ b/common/lib/transport/comettransport.js @@ -243,7 +243,7 @@ var CometTransport = (function() { var items = this.decodeResponse(responseData); if(items && items.length) for(var i = 0; i < items.length; i++) - this.onChannelMessage(items[i]); + this.onChannelMessage(ProtocolMessage.fromDecoded(items[i])); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'CometTransport.onData()', 'Unexpected exception handing channel event: ' + e.stack); } From a4af9486cf9f29cfc8050ec4113125a9e2adfe11 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 13:46:37 +0100 Subject: [PATCH 04/14] Fix enter7 test: second presence message can come through as an update as same clientId --- spec/realtime/presence.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/realtime/presence.test.js b/spec/realtime/presence.test.js index d7ebc20b91..08f7c388f2 100644 --- a/spec/realtime/presence.test.js +++ b/spec/realtime/presence.test.js @@ -454,12 +454,12 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { exports.enter7 = function(test) { var clientRealtime; try { - test.expect(2); + test.expect(3); clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); var clientChannel = clientRealtime.channels.get('presenceEnter7'); /* listen for the enter event, test is complete when received */ var presenceHandler = function(presenceMsg) { - if(this.event == 'enter' && presenceMsg.data == 'second') { + if(presenceMsg.data == 'second') { test.ok(true, 'Second presence event received'); closeAndFinish(test, clientRealtime); } @@ -478,6 +478,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { closeAndFinish(test, clientRealtime); return; } + test.ok(true, 'Detached from channel'); clientChannel.presence.on(presenceHandler); clientChannel.presence.enter('second', function(err){ if(err) { From 2b2d8e931b5ce445f391cceab3f5505630397183 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 13:47:43 +0100 Subject: [PATCH 05/14] Fix enter3 test: give valid failure branch same no. of expectations as the other --- spec/realtime/presence.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/realtime/presence.test.js b/spec/realtime/presence.test.js index 08f7c388f2..dcbf05f9b2 100644 --- a/spec/realtime/presence.test.js +++ b/spec/realtime/presence.test.js @@ -288,7 +288,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { // is an acceptable result. Throwing an uncaught exception (the behaviour // that we're testing for) isn't. if(err) { - test.ok(true, 'Enter failed with error: ' + err); + test.ok(true, 'Enter failed with error: ' + JSON.stringify(err)); + test.equal(err.code, 40400) done(); return; } From 433dba4f9fab9c2290f011477b155590ae66ea31 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 18 Sep 2015 16:18:24 +0100 Subject: [PATCH 06/14] Rewrite the realtime presence tests to be less fragile To: - not have any shared connection state between tests - not rely on having a thee-second timeout at the end of each test - not use the same channel in every test - be a bit DRYer among other changes --- spec/realtime/presence.test.js | 1178 +++++++++++++------------------- 1 file changed, 476 insertions(+), 702 deletions(-) diff --git a/spec/realtime/presence.test.js b/spec/realtime/presence.test.js index dcbf05f9b2..3a08ff9f32 100644 --- a/spec/realtime/presence.test.js +++ b/spec/realtime/presence.test.js @@ -1,14 +1,79 @@ "use strict"; define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { - var exports = {}, + var exports = {}, _exports = {}, displayError = helper.displayError, closeAndFinish = helper.closeAndFinish, monitorConnection = helper.monitorConnection; - var rest, realtime, authToken, authToken2; + var rest, authToken, authToken2; var testClientId = 'testclient', testClientId2 = 'testclient2'; - var presenceChannel; + + var createListenerChannel = function(channelName, callback) { + var channel, realtime; + try { + realtime = helper.AblyRealtime(); + realtime.connection.on('connected', function() { + console.log("Listener connected"); + channel = realtime.channels.get(channelName); + channel.attach(function(err) { + console.log("Listener attached to channel " + channelName); + callback(err, realtime, channel); + }); + }); + } catch(err) { + callback(err, realtime); + } + }; + + var listenerFor = function(eventName, expectedClientId) { + return function(test, channel, callback) { + var presenceHandler = function(presmsg) { + if(this.event === eventName) { + test.ok(true, 'Presence ' + eventName + ' received'); + if(expectedClientId !== undefined) { + test.equal(presmsg.clientId, expectedClientId, 'Verify correct clientId'); + } + channel.presence.off(presenceHandler); + callback(); + } + }; + channel.presence.on(presenceHandler); + }; + }; + + var runTestWithEventListener = function(test, channel, eventListener, testRunner) { + try { + createListenerChannel(channel, function(err, listenerRealtime, presenceChannel){ + if(err) { + test.ok(false, displayError(err)); + closeAndFinish(test, listenerRealtime); + return; + } + console.log("presenceChannel:", presenceChannel.name); + + async.parallel([ + function(cb) { + eventListener(test, presenceChannel, cb); + }, + testRunner + ], function(err, res) { + console.log("in callback, err = ", err); + if(err) { + test.ok(false, displayError(err)); + } + // testRunner might or might not call back with an open realtime + var openConnections = (res[1] && res[1].close) ? + [listenerRealtime, res[1]] : + listenerRealtime; + closeAndFinish(test, openConnections); + }); + }); + } catch(e) { + test.ok(false, 'test failed with exception: ' + e.stack); + test.done(); + } + }; exports.setupauth = function(test) { test.expect(1); @@ -22,486 +87,322 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); }; - /** - * Set up the prerequisites of the presence tests below. - * - * A generic (anonymous connection) is established which listens - * for events on the presence object for a given channel. - * Individual tests listen on this object from time to time. - * - * An authToken is created that is associated with a specific - * client id, which is used to send events on the presence channel. - * @param test + /* + * Create authTokens associated with specific clientIds */ - exports.setuppresence = function(test) { - var expects = 0; - async.series( - [ - function(cb) { - test.expect(++expects); - try { - rest = helper.AblyRest(); - rest.auth.requestToken({clientId:testClientId}, function(err, tokenDetails) { - if(err) { - test.ok(false, displayError(err)); - cb(err); - return; - } - authToken = tokenDetails.id; - test.equal(tokenDetails.clientId, testClientId, 'Verify client id'); - test.expect(++expects); - rest.auth.requestToken({clientId:testClientId2}, function(err, tokenDetails) { - if(err) { - test.ok(false, displayError(err)); - cb(err); - return; - } - authToken2 = tokenDetails.id; - test.equal(tokenDetails.clientId, testClientId2, 'Verify client id (2)'); - cb(null); - }); - }); - } catch(err) { - test.ok(false, 'Test failed with exception: ' + err.stack); - cb(err); - } - }, - function(cb) { - test.expect(++expects); - try { - realtime = helper.AblyRealtime(); - realtime.connection.on('connected', function() { - presenceChannel = realtime.channels.get('presence0'); - presenceChannel.attach(function(err) { - if(err) - test.ok(false, 'Attach failed with error: ' + err); - else - test.ok(true, 'Attach to channel 0 with no options'); - cb(err); - }); - }); - monitorConnection(test, realtime); - } catch(err) { - test.ok(false, 'Test failed with exception: ' + err.stack); - cb(err); - } + exports.setupPresenceTokens = function(test) { + test.expect(2); + try { + rest = helper.AblyRest(); + rest.auth.requestToken({clientId:testClientId}, function(err, tokenDetails) { + if(err) { + test.ok(false, displayError(err)); + test.done(); + return; } - ], function() { - test.done(); - } - ); + authToken = tokenDetails.id; + test.equal(tokenDetails.clientId, testClientId, 'Verify client id'); + + rest.auth.requestToken({clientId:testClientId2}, function(err, tokenDetails) { + if(err) { + test.ok(false, displayError(err)); + test.done(); + return; + } + authToken2 = tokenDetails.id; + test.equal(tokenDetails.clientId, testClientId2, 'Verify client id (2)'); + test.done(); + }); + }); + } catch(err) { + test.ok(false, 'Test failed with exception: ' + err.stack); + test.done(); + } }; + /* * Attach to channel, enter presence channel with data and await entered event */ - exports.enter0 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(2); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceAttachAndEnter = function(test) { + test.expect(2); + var channelName = 'attachAndEnter'; + var attachAndEnter = function(cb) { /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter('Test client data (enter0)', function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + if(!err) + test.ok(true, 'Presence event sent'); + cb(err, clientRealtime); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter0 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), attachAndEnter); }; /* * Enter presence channel without prior attach and await entered event */ - exports.enter1 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(2); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceEnterWithoutAttach = function(test) { + test.expect(2); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var channelName = 'enterWithoutAttach'; + var enterWithoutAttach = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); - clientChannel.presence.enter('Test client data (enter1)', function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + var clientChannel = clientRealtime.channels.get(channelName); + clientChannel.presence.enter('Test client data (enterWithoutAttach)', function(err) { + if(!err) + test.ok(true, 'Presence event sent'); + cb(err, clientRealtime); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter1 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), enterWithoutAttach); }; /* * Enter presence channel without prior connect and await entered event */ - exports.enter2 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(2); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceEnterWithoutConnect = function(test) { + test.expect(2); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); - /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); - clientChannel.presence.enter('Test client data (enter2)', function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + var channelName = 'enterWithoutConnect'; + var enterWithoutConnect = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var clientChannel = clientRealtime.channels.get(channelName); + clientChannel.presence.enter('Test client data (enterWithoutConnect)', function(err) { + if(!err) + test.ok(true, 'Presence event sent'); + cb(err, clientRealtime); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter2 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), enterWithoutConnect); }; /* * Attach to channel, enter presence channel (without waiting for attach callback), detach * from channel immediately in 'attached' callback */ - exports.enter3 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; + exports.presenceEnterDetachRace = function(test) { + // Can't use runTestWithEventListener helper as one of the successful + // outcomes is an error in presence enter, in which case listenForEventOn + // will not run its callback + var channelName = 'enterDetachRace'; try { test.expect(4); /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); + + createListenerChannel(channelName, function(err, listenerRealtime, presenceChannel){ + if(err) { + test.ok(false, displayError(err)); + closeAndFinish(test, listenerRealtime); + return; } - }; - presenceChannel.presence.on(presenceHandler); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); - clientRealtime.connection.on('connected', function() { - /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); - clientChannel.attach(function(err) { - if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Attached'); - clientChannel.detach(function(err) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + + listenerFor('enter')(test, presenceChannel, function() { + test.ok(true, 'Presence event received'); + closeAndFinish(test, [listenerRealtime, clientRealtime]); + }); + + clientRealtime.connection.on('connected', function() { + /* get channel, attach, and enter */ + var clientChannel = clientRealtime.channels.get(channelName); + clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Detach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, [listenerRealtime, clientRealtime]); return; } - test.ok(true, 'Detached'); + test.ok(true, 'Attached'); + clientChannel.detach(function(err) { + if(err) { + test.ok(false, 'Detach failed with error: ' + displayError(err)); + closeAndFinish(test, [listenerRealtime, clientRealtime]); + return; + } + test.ok(true, 'Detached'); + }); + }); + clientChannel.presence.enter('Test client data (enter3)', function(err) { + // Note: either an error (pending messages failed to send due to detach) + // or a success (pending messages were pushed out before the detach) + // is an acceptable result. Throwing an uncaught exception (the behaviour + // that we're testing for) isn't. + if(err) { + test.ok(true, 'Enter failed with error: ' + JSON.stringify(err)); + test.equal(err.code, 40400); + closeAndFinish(test, [listenerRealtime, clientRealtime]); + return; + } + test.ok(true, 'Presence event sent'); + /* if presence event gets sent, 5th assertion happens and test + * finishes in the presence event handler */ }); }); - clientChannel.presence.enter('Test client data (enter3)', function(err) { - // Note: either an error (pending messages failed to send due to detach) - // or a success (pending messages were pushed out before the detach) - // is an acceptable result. Throwing an uncaught exception (the behaviour - // that we're testing for) isn't. - if(err) { - test.ok(true, 'Enter failed with error: ' + JSON.stringify(err)); - test.equal(err.code, 40400) - done(); - return; - } - test.ok(true, 'Presence event sent'); - /* done() is called in presence event handler */ - }); + monitorConnection(test, clientRealtime); }); - monitorConnection(test, clientRealtime); } catch(e) { - test.ok(false, 'presence.enter3 failed with exception: ' + e.stack); - done(); + test.ok(false, 'presence.enterDetachRace failed with exception: ' + e.stack); + test.done(); } }; /* * Attach to channel, enter presence channel with a callback but no data and await entered event */ - exports.enter4 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(2); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceEnterWithCallback = function(test) { + test.expect(2); + var channelName = 'enterWithCallback'; + var enterWithCallback = function(cb) { /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter(function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + if(!err) + test.ok(true, 'Presence event sent'); + cb(err, clientRealtime); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter4 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), enterWithCallback); }; /* * Attach to channel, enter presence channel with neither callback nor data and await entered event */ - exports.enter5 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(1); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceEnterWithNothing = function(test) { + test.expect(1); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var channelName = 'enterWithNothing'; + var enterWithNothing = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter(); + cb(null, clientRealtime); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter5 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), enterWithNothing); }; /* * Attach to channel, enter presence channel with data but no callback and await entered event */ - exports.enter6 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(1); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function() { - if(this.event == 'enter') { - test.ok(true, 'Presence event received'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceEnterWithData = function(test) { + test.expect(1); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var channelName = 'enterWithData'; + var enterWithData = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter('Test client data (enter6)'); + cb(null, clientRealtime); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter6 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('enter'), enterWithData); }; /* * Enter presence channel (without attaching), detach, then enter again to reattach */ - exports.enter7 = function(test) { - var clientRealtime; - try { - test.expect(3); - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); - var clientChannel = clientRealtime.channels.get('presenceEnter7'); - /* listen for the enter event, test is complete when received */ + exports.presenceEnterDetachEnter = function(test) { + test.expect(4); + + var channelName = 'enterDetachEnter'; + var secondEventListener = function(test, channel, callback) { var presenceHandler = function(presenceMsg) { if(presenceMsg.data == 'second') { test.ok(true, 'Second presence event received'); - closeAndFinish(test, clientRealtime); + channel.presence.off(presenceHandler); + callback(); } }; + channel.presence.on(presenceHandler); + }; + var enterDetachEnter = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.once('connected', function() { clientChannel.presence.enter('first', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - closeAndFinish(test, clientRealtime); + cb(err, clientRealtime); return; } test.ok(true, 'Entered presence first time'); clientChannel.detach(function(err) { if(err) { - test.ok(false, 'Detach failed with error: ' + err); - closeAndFinish(test, clientRealtime); + cb(err, clientRealtime); return; } test.ok(true, 'Detached from channel'); - clientChannel.presence.on(presenceHandler); clientChannel.presence.enter('second', function(err){ - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - closeAndFinish(test, clientRealtime); - return; - } + if(!err) + test.ok(true, 'Second presence enter sent'); + cb(err, clientRealtime); }); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.enter7 failed with exception: ' + e.stack); - closeAndFinish(test, clientRealtime); - } + }; + + runTestWithEventListener(test, channelName, secondEventListener, enterDetachEnter); }; /* * Enter invalid presence channel (without attaching), check callback was called with error */ - exports.enter8 = function(test) { + exports.presenceEnterInvalid = function(test) { var clientRealtime; try { test.expect(2); @@ -510,8 +411,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { clientRealtime.connection.once('connected', function() { clientChannel.presence.enter('clientId', function(err) { if(err) { - test.ok(true, 'Enter correctly failed with error: ' + err); - test.equal(err.code, 40010, 'Correct error code') + test.ok(true, 'Enter correctly failed with error: ' + displayError(err)); + test.equal(err.code, 40010, 'Correct error code'); closeAndFinish(test, clientRealtime); return; } @@ -521,7 +422,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); monitorConnection(test, clientRealtime); } catch(e) { - test.ok(false, 'presence.enter8 failed with exception: ' + e.stack); + test.ok(false, 'presenceEnterInvalid failed with exception: ' + e.stack); closeAndFinish(test, clientRealtime); } }; @@ -529,332 +430,242 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { /* * Attach to channel, enter+leave presence channel and await leave event */ - exports.leave0 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(4); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function(presenceMessage) { - //console.log('Event received on presence channel: event = ' + this.event + ', clientId = ' + presenceMessage.clientId + ', clientData = ' + presenceMessage.clientData); - if(this.event == 'leave') { - test.ok(true, 'Leave event received'); - test.equal(presenceMessage.clientId, testClientId, 'Presence event received with clientId'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + exports.presenceEnterAndLeave = function(test) { + test.expect(3); + + var channelName = 'enterAndLeave'; + var enterAndLeave = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter('Test client data (leave0)', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } test.ok(true, 'Presence event sent'); }); clientChannel.presence.leave(function(err) { - if(err) { - test.ok(false, 'Leave failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + if(!err) + test.ok(true, 'Presence event sent'); + cb(err, clientRealtime); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.leave0 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('leave'), enterAndLeave); }; /* * Attach to channel, enter presence channel, update data, and await update event */ - exports.update0 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - var newData = "New data"; - try { - test.expect(5); + exports.presenceEnterUpdate = function(test) { + test.expect(5); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var newData = "New data"; + var channelName = 'enterUpdate'; + var eventListener = function(test, channel, callback) { + var presenceHandler = function(presenceMsg) { + if(this.event == 'update') { + test.ok(true, 'Update event received'); + test.equal(presenceMsg.clientId, testClientId, 'Check presence event has correct clientId'); + test.equal(presenceMsg.data, newData, 'Check presence event has correct data'); + channel.presence.off(presenceHandler); + callback(); + } + }; + channel.presence.on(presenceHandler); + }; + var enterUpdate = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { - var clientChannel = clientRealtime.channels.get('presenceUpdate0'); - - /* listen for the enter event, test is complete when received */ - var presenceHandler = function(presenceMessage) { - if(this.event == 'update') { - test.ok(true, 'Update event received'); - test.equal(presenceMessage.clientId, testClientId, 'Check presence event has correct clientId'); - test.equal(presenceMessage.data, newData, 'Check presence event has correct data'); - done(); - clientChannel.presence.off(presenceHandler); - } - }; - clientChannel.presence.on(presenceHandler); - + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter('Original data', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } - test.ok(true, 'Presence event sent'); - }); - clientChannel.presence.update(newData, function(err) { - if(err) { - test.ok(false, 'Update failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + test.ok(true, 'Presence enter sent'); + clientChannel.presence.update(newData, function(err) { + if(!err) + test.ok(true, 'Presence update sent'); + cb(err, clientRealtime); + }); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.update0 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, eventListener, enterUpdate); }; /* * Attach to channel, enter presence channel and get presence */ - exports.get0 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(2); - /* listen for the enter event, test is complete when received */ + exports.presenceEnterGet = function(test) { + test.expect(5); + var channelName = 'enterGet'; + var testData = 'some data for presenceEnterGet'; + var eventListener = function(test, channel, callback) { var presenceHandler = function() { - presenceChannel.presence.get(function(err, presenceMembers) { + test.equal(this.event, 'enter', 'Expect the only presence event to be enter'); + channel.presence.get(function(err, presenceMembers) { if(err) { - test.ok(false, 'Presence get() failed with error: ' + err); - done(); + callback(err); return; } - var testClientPresent = false; - if(presenceMembers) - for(var i = 0; i < presenceMembers.length; i++) - if(presenceMembers[i].clientId == testClientId) - testClientPresent = true; - test.ok(testClientPresent, 'Expected test client in set of members'); - done(); - presenceChannel.presence.off(presenceHandler); + test.equal(presenceMembers.length, 1, 'Expect test client to be the only member present'); + test.equal(presenceMembers[0].clientId, testClientId, 'Expected test clientId to be correct'); + test.equal(presenceMembers[0].data, testData, 'Expected data to be correct'); + channel.presence.off(presenceHandler); + callback(); }); }; - presenceChannel.presence.on(presenceHandler); - - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + channel.presence.on(presenceHandler); + }; + var enterGet = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } - clientChannel.presence.enter('Test client data (get0)', function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence event sent'); + clientChannel.presence.enter(testData, function(err) { + if(!err) + test.ok(true, 'Presence enter sent'); + cb(err, clientRealtime); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.get0 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, eventListener, enterGet); }; /* * Attach to channel, enter+leave presence channel and get presence */ - exports.get1 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(3); - /* listen for the enter event, test is complete when received */ - var testClientData = 'Test client data (get1)'; + exports.presenceEnterLeaveGet = function(test) { + test.expect(3); + var channelName = 'enterLeaveGet'; + var eventListener = function(test, channel, callback) { var presenceHandler = function() { + // Ignore the first (enter) event if(this.event == 'leave') { - presenceChannel.presence.get(function(err, presenceMembers) { + channel.presence.get(function(err, presenceMembers) { if(err) { - test.ok(false, 'Presence get() failed with error: ' + err); - done(); + callback(err); return; } - //console.log(require('util').inspect(presenceMembers)); - var testClientPresent = false; - if(presenceMembers) { - for(var i = 0; i < presenceMembers.length; i++) - if(presenceMembers[i].clientId == testClientId && presenceMembers[i].clientData == testClientData) - testClientPresent = true; - } - test.ok(!testClientPresent, 'Expected test client to be absent from set of members'); - done(); - presenceChannel.presence.off(presenceHandler); - + test.equal(presenceMembers.length, 0, 'Expect presence set to be empty'); + channel.presence.off(presenceHandler); + callback(); }); } }; - presenceChannel.presence.on(presenceHandler); - - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + channel.presence.on(presenceHandler); + }; + var enterLeaveGet = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } - clientChannel.presence.enter(testClientData, function(err) { + clientChannel.presence.enter('testClientData', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } test.ok(true, 'Presence enter event sent'); clientChannel.presence.leave(function(err) { - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); - return; - } - test.ok(true, 'Presence leave event sent'); + if(!err) + test.ok(true, 'Presence leave event sent'); + cb(err, clientRealtime); }); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.get1 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, eventListener, enterLeaveGet); }; - /* + /* * Attach to channel, enter+leave presence, detatch again, and get presence history */ - exports.history0 = function(test) { + exports.presenceHistory = function(test) { + test.expect(4); var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { + var channelName = 'history'; + var testClientData = 'Test client data (history0)'; + var queryPresenceHistory = function(channel) { + channel.presence.history(function(err, resultPage) { + if(err) { + test.ok(false, displayError(err)); closeAndFinish(test, clientRealtime); - }, 3000); - } + return; + } + + var presenceMessages = resultPage.items; + test.equal(presenceMessages.length, 2, 'Verify correct number of presence messages found'); + var actions = presenceMessages.map(function(msg){return msg.action;}).sort(); + test.deepEqual(actions, [2,3], 'Verify presenceMessages have correct actions'); + test.equal(presenceMessages[0].data, testClientData, 'Verify first presenceMessages has correct data'); + test.equal(presenceMessages[1].data, testClientData, 'Verify second presenceMessages has correct data'); + closeAndFinish(test, clientRealtime); + }); }; try { - test.expect(4); - /* listen for the enter event, test is complete when received */ - var testClientData = 'Test client data (history0)'; - var queryPresenceHistory = function(channel) { - channel.presence.history(function(err, resultPage) { - if(err) { - test.ok(false, displayError(err)); - done(); - return; - } - - var presenceMessages = resultPage.items; - test.equal(presenceMessages.length, 2, 'Verify correct number of presence messages found'); - var actions = presenceMessages.map(function(msg){return msg.action}).sort(); - test.deepEqual(actions, [2,3], 'Verify presenceMessages have correct actions'); - test.equal(presenceMessages[0].data, testClientData, 'Verify first presenceMessages has correct data'); - test.equal(presenceMessages[1].data, testClientData, 'Verify second presenceMessages has correct data'); - done(); - }); - }; - /* set up authenticated connection */ clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presenceHistory0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } clientChannel.presence.enter(testClientData, function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + test.ok(false, 'Enter failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } clientChannel.presence.leave(function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + test.ok(false, 'Enter failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } clientChannel.detach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } queryPresenceHistory(clientChannel); @@ -866,34 +677,34 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { monitorConnection(test, clientRealtime); } catch(e) { test.ok(false, 'presence.history0 failed with exception: ' + e.stack); - done(); + closeAndFinish(test, clientRealtime); } }; - exports.history_until_attach = function(test) { + exports.presenceHistoryUntilAttach = function(test) { + test.expect(6); + var clientRealtime = helper.AblyRealtime({clientId: testClientId}); - var clientChannel = clientRealtime.channels.get('presenceHistoryUntilAttach'); + var channelName = 'historyUntilAttach'; + var clientChannel = clientRealtime.channels.get(channelName); var testClientData = 'Test client data (history0)'; - var done = function() { - test.done(); clientRealtime.close(); - }; var attachEnterAndLeave = function(callback) { clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } clientChannel.presence.enter(testClientData, function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + test.ok(false, 'Enter failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } clientChannel.presence.leave(function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + test.ok(false, 'Enter failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } callback(); @@ -933,8 +744,6 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { } ]; - test.expect(6); - try { clientRealtime.connection.on('connected', function() { /* Attach, enter, leave, then detach. Then attach, enter, and @@ -944,34 +753,27 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { attachEnterAndLeave(function() { clientChannel.detach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime); return; } attachEnterAndLeave(function() { async.parallel(tests, function(err){ if(err) { test.ok(false, displayError(err)); - done(); + closeAndFinish(test, clientRealtime); return; } - done(); + closeAndFinish(test, clientRealtime); }); }); }); }); }); - var exitOnState = function(state) { - clientRealtime.connection.on(state, function () { - test.ok(false, 'connection to server failed'); - done(); - }); - }; - exitOnState('failed'); - exitOnState('suspended'); + monitorConnection(test, clientRealtime); } catch(e) { test.ok(false, 'presence.history_until_attach failed with exception: ' + e.stack); - done(); + closeAndFinish(test, clientRealtime); } }; @@ -979,40 +781,36 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { * Attach to channel, enter presence channel, then initiate second * connection, seeing existing member in message subsequent to second attach response */ - exports.attach0 = function(test) { + exports.presenceSecondConnection = function(test) { + test.expect(3); + var clientRealtime1, clientRealtime2; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, [clientRealtime1, clientRealtime2]); - }, 3000); - } - }; + var channelName = 'secondConnection'; try { - test.expect(3); /* set up authenticated connection */ clientRealtime1 = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime1.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel1 = clientRealtime1.channels.get('presence1'); + var clientChannel1 = clientRealtime1.channels.get(channelName); clientChannel1.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime1); return; } clientChannel1.presence.enter('Test client data (attach0)', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + test.ok(false, 'Enter failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime1); return; } test.ok(true, 'Presence event sent'); + }); + clientChannel1.presence.on('enter', function() { clientChannel1.presence.get(function(err, presenceMembers1) { if(err) { - test.ok(false, 'Presence get() failed with error: ' + err); - done(); + test.ok(false, 'Presence get() failed with error: ' + displayError(err)); + closeAndFinish(test, clientRealtime1); return; } test.equal(presenceMembers1.length, 1, 'Member present'); @@ -1021,23 +819,23 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, authToken: authToken2 }); clientRealtime2.connection.on('connected', function() { /* get channel, attach */ - var clientChannel2 = clientRealtime2.channels.get('presence1'); + var clientChannel2 = clientRealtime2.channels.get(channelName); clientChannel2.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + test.ok(false, 'Attach failed with error: ' + displayError(err)); + closeAndFinish(test, [clientRealtime1, clientRealtime2]); return; } clientChannel2.presence.on('present', function() { /* get the channel members and verify testclient is there */ clientChannel2.presence.get(function(err, presenceMembers2) { if(err) { - test.ok(false, 'Presence get() failed with error: ' + err); - done(); + test.ok(false, 'Presence get() failed with error: ' + displayError(err)); + closeAndFinish(test, [clientRealtime1, clientRealtime2]); return; } test.deepEqual(presenceMembers1, presenceMembers2, 'Verify member presence is indicated after attach'); - done(); + closeAndFinish(test, [clientRealtime1, clientRealtime2]); }); }); }); @@ -1049,8 +847,8 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); monitorConnection(test, clientRealtime1); } catch(e) { - test.ok(false, 'presence.attach0 failed with exception: ' + e.stack); - done(); + test.ok(false, 'presenceSecondConnection failed with exception: ' + e.stack); + closeAndFinish(test, [clientRealtime1, clientRealtime2]); } }; @@ -1058,18 +856,14 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { * Attach and enter channel on two connections, seeing * both members in presence set */ - exports.member0 = function(test) { + exports.presenceTwoMembers = function(test) { + test.expect(4); + var clientRealtime1, clientRealtime2, clientChannel1, clientChannel2; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, [clientRealtime1, clientRealtime2]); - }, 3000); - } + var done = function() { + closeAndFinish(test, [clientRealtime1, clientRealtime2]); }; try { - test.expect(4); /* set up authenticated connections */ async.parallel([ function(cb1) { @@ -1080,7 +874,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { clientChannel1 = clientRealtime1.channels.get('presence2'); clientChannel1.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); + test.ok(false, 'Attach failed with error: ' + displayError(err)); cb1(err); return; } @@ -1090,7 +884,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); clientChannel1.presence.enter(data, function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); + test.ok(false, 'Enter failed with error: ' + displayError(err)); cb1(err); return; } @@ -1108,22 +902,33 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { clientChannel2 = clientRealtime2.channels.get('presence2'); clientChannel2.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); + test.ok(false, 'Attach failed with error: ' + displayError(err)); cb2(err); return; } - clientChannel2.presence.on('enter', function(presenceEvent){ - if(presenceEvent.data == data) - cb2(); - }); clientChannel2.presence.enter(data, function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); + test.ok(false, 'Enter failed with error: ' + displayError(err)); cb2(err); return; } test.ok(true, 'Presence event sent'); }); + // Wait for both enter events to be received on clientChannel2 before calling back + var waitForClient = function(clientId) { + return function(onEnterCb) { + clientChannel2.presence.on('enter', function(presenceEvent){ + if(presenceEvent.clientId == clientId) + onEnterCb(); + }); + }; + }; + async.parallel([ + waitForClient(testClientId), + waitForClient(testClientId2) + ], function() { + cb2(); + }); }); }); monitorConnection(test, clientRealtime2); @@ -1131,7 +936,7 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { ], function() { clientChannel2.presence.get(function(err, members) { if(err) { - test.ok(false, 'Presence.get() failed with error: ' + err); + test.ok(false, 'Presence.get() failed with error: ' + displayError(err)); done(); return; } @@ -1149,100 +954,76 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { /* * Attach to channel, enter presence channel, disconnect and await leave event */ - exports.connection0 = function(test) { - var clientRealtime; - var isDone = false, done = function() { - if(!isDone) { - isDone = true; - setTimeout(function() { - closeAndFinish(test, clientRealtime); - }, 3000); - } - }; - try { - test.expect(3); - /* listen for the enter event, test is complete when received */ - var presenceHandler = function(presenceMessage) { - //console.log('Event received on presence channel: event = ' + this.event + ', clientId = ' + presenceMessage.clientId + ', clientData = ' + presenceMessage.clientData); - if(this.event == 'leave') { - test.ok(true, 'Leave event received'); - test.equal(presenceMessage.clientId, testClientId, 'Presence event received with clientId'); - done(); - presenceChannel.presence.off(presenceHandler); - } - }; - presenceChannel.presence.on(presenceHandler); + exports.presenceLeaveOnDisconnect = function(test) { + test.expect(3); - /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var channelName = 'leaveOnDisconnect'; + var leaveOnDisconnect = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); clientRealtime.connection.on('connected', function() { /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get('presence0'); + var clientChannel = clientRealtime.channels.get(channelName); clientChannel.attach(function(err) { if(err) { - test.ok(false, 'Attach failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } clientChannel.presence.enter('Test client data (connection0)', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - done(); + cb(err, clientRealtime); return; } test.ok(true, 'Presence event sent'); - setTimeout(function() { - /* once enter event is confirmed as having been - * delivered, close the connection */ - clientRealtime.close(); - }, 1000); + /* once enter event is confirmed as having been + * delivered, close the connection */ + clientRealtime.close(); + cb(null, clientRealtime); }); }); }); monitorConnection(test, clientRealtime); - } catch(e) { - console.log('presence.leave0 failed with exception: ' + e.stack); - test.ok(false, 'presence.leave0 failed with exception: ' + e.stack); - done(); - } + }; + + runTestWithEventListener(test, channelName, listenerFor('leave', testClientId), leaveOnDisconnect); }; /* * Enter presence channel (without attaching), close the connection, * reconnect, then enter again to reattach */ - exports.connection1 = function(test) { - var clientRealtime; - try { - test.expect(2); - clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); - var clientChannel = clientRealtime.channels.get('presenceConnection1'); + exports.presenceEnterAfterClose = function(test) { + test.expect(3); + + var channelName = "enterAfterClose"; + var secondEnterListener = function(test, channel, callback) { var presenceHandler = function(presenceMsg) { if(this.event == 'enter' && presenceMsg.data == 'second') { test.ok(true, 'Second presence event received'); - closeAndFinish(test, clientRealtime); + channel.presence.off(presenceHandler); + callback(); } }; + channel.presence.on(presenceHandler); + }; + var enterAfterClose = function(cb) { + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, authToken: authToken }); + var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.once('connected', function() { /* get channel and enter (should automatically attach) */ clientChannel.presence.enter('first', function(err) { if(err) { - test.ok(false, 'Enter failed with error: ' + err); - closeAndFinish(test, clientRealtime); + cb(err, clientRealtime); return; } test.ok(true, 'Entered presence first time'); clientRealtime.close(); clientRealtime.connection.once('closed', function() { - clientRealtime.connection.once('connected', function(){ + clientRealtime.connection.once('connected', function() { //Should automatically reattach - clientChannel.presence.on(presenceHandler); - clientChannel.presence.enter('second', function(err){ - if(err) { - test.ok(false, 'Enter failed with error: ' + err); - closeAndFinish(test, clientRealtime); - return; - } + clientChannel.presence.enter('second', function(err) { + if(!err) + test.ok(true, 'Second presence enter sent'); + cb(err, clientRealtime); }); }); clientRealtime.connection.connect(); @@ -1250,21 +1031,21 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); }); monitorConnection(test, clientRealtime); - } catch(e) { - test.ok(false, 'presence.connection1 failed with exception: ' + e.stack); - closeAndFinish(test, clientRealtime); - } + }; + + runTestWithEventListener(test, channelName, secondEnterListener, enterAfterClose); }; /* * Try to enter presence channel on a closed connection and check error callback */ - exports.enter_closed = function(test) { + exports.presenceEnterClosed = function(test) { var clientRealtime; + var channelName = "enterClosed"; try { test.expect(2); clientRealtime = helper.AblyRealtime(); - var clientChannel = clientRealtime.channels.get('enter_closed'); + var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.on('connected', function() { clientRealtime.close(); clientChannel.presence.enterClient('clientId', function(err) { @@ -1275,17 +1056,10 @@ define(['ably', 'shared_helper', 'async'], function(Ably, helper, async) { }); monitorConnection(test, clientRealtime); } catch(e) { - test.ok(false, 'presence.enter_close failed with exception: ' + e.stack); - done(); + test.ok(false, 'presenceEnterClosed failed with exception: ' + e.stack); + closeAndFinish(test, clientRealtime); } }; - exports.clear99 = function(test) { - /* delay before closing, to allow final tests to see events on connections */ - setTimeout(function() { - closeAndFinish(test, realtime); - }, 3000); - }; - return module.exports = helper.withTimeout(exports); }); From cd1a826928373c9ec4b7a66569a7391efa69f25a Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 10:51:59 +0100 Subject: [PATCH 07/14] Upgrade tests: fix tests using the same channel name --- spec/realtime/upgrade.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/realtime/upgrade.test.js b/spec/realtime/upgrade.test.js index 4d5a581a8f..0aa40851be 100644 --- a/spec/realtime/upgrade.test.js +++ b/spec/realtime/upgrade.test.js @@ -153,7 +153,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { var realtime = helper.AblyRealtime(transportOpts); /* subscribe to event */ - var rtChannel = realtime.channels.get('publishpostupgrade0'); + var rtChannel = realtime.channels.get('publishpostupgrade1'); rtChannel.subscribe('event0', function(msg) { test.expect(2); test.ok(true, 'Received event0'); @@ -169,7 +169,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { /* publish event */ var testMsg = 'Hello world'; - var restChannel = rest.channels.get('publishpostupgrade0'); + var restChannel = rest.channels.get('publishpostupgrade1'); var connectionManager = realtime.connection.connectionManager; connectionManager.on('transport.active', function(transport) { if(transport.toString().indexOf('/comet/') > -1) { From 72ecdb6e27450ffb82e0cd7d2101db35f0deca85 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 12:28:53 +0100 Subject: [PATCH 08/14] =?UTF-8?q?Event=20emitter=20test:=20don=E2=80=99t?= =?UTF-8?q?=20bother=20reclosing=20a=20closed=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/realtime/event_emitter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/realtime/event_emitter.test.js b/spec/realtime/event_emitter.test.js index efd0906e0d..36cef78f58 100644 --- a/spec/realtime/event_emitter.test.js +++ b/spec/realtime/event_emitter.test.js @@ -44,7 +44,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { delete expectedConnectionEvents[index]; test.ok(true, this.event + ' connection event received'); if(this.event == 'closed') { - closeAndFinish(test, realtime); + test.done(); } } else { test.ok(false, 'Unexpected ' + this.event + ' event received'); From 0461ad1a7409f457e448d16b4cce2a361d3d334a Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 12:29:13 +0100 Subject: [PATCH 09/14] Spec helper: activeProtocol may be undefined or null --- spec/common/modules/shared_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/common/modules/shared_helper.js b/spec/common/modules/shared_helper.js index 838f5949c1..5cbb4c5160 100644 --- a/spec/common/modules/shared_helper.js +++ b/spec/common/modules/shared_helper.js @@ -55,7 +55,7 @@ define(['spec/common/modules/testapp_module', 'spec/common/modules/client_module } function callbackOnClose(realtime, callback) { - if(realtime.connection.connectionManager.activeProtocol === null) { + if(!realtime.connection.connectionManager.activeProtocol) { console.log("No transport established; closing connection and calling test.done()") realtime.close(); callback(); From b72cbca22ce7d5a8c550bf892436c59a38336f3e Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 13:16:27 +0100 Subject: [PATCH 10/14] Connection events test: shortcircuit on IE --- spec/browser/connection.test.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/spec/browser/connection.test.js b/spec/browser/connection.test.js index 1b18ac4647..184e7d7d6e 100644 --- a/spec/browser/connection.test.js +++ b/spec/browser/connection.test.js @@ -7,11 +7,21 @@ define(['ably', 'shared_helper'], function(Ably, helper) { simulateDroppedConnection = helper.simulateDroppedConnection, Defaults = Ably.Realtime.Defaults; - function stopIfUnsupported(test) { + function supportedBrowser(test) { if(document.body.ononline === undefined) { - test.done(); - return; + console.log("Online events not supported; skipping connection.test.js"); + return false; + } + + // IE doesn't support creating your own events with new + try { + var testEvent = new Event("foo"); + } catch(e) { + console.log("On IE; skipping connection.test.js"); + return false; } + + return true; } exports.setup_realtime = function(test) { @@ -27,8 +37,6 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }; exports.device_going_offline_causes_disconnected_state = function(test) { - stopIfUnsupported(test); - var realtime = helper.AblyRealtime(), connection = realtime.connection, offlineEvent = new Event('offline', {'bubbles': true}); @@ -57,8 +65,6 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }; exports.device_going_online_causes_disconnected_connection_to_reconnect_immediately = function(test) { - stopIfUnsupported(test); - var realtime = helper.AblyRealtime(), connection = realtime.connection, onlineEvent = new Event('online', {'bubbles': true}); @@ -90,8 +96,6 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }; exports.device_going_online_causes_suspended_connection_to_reconnect_immediately = function(test) { - stopIfUnsupported(test); - Defaults.disconnectTimeout = 100; // retry connection more frequently Defaults.suspendedTimeout = 1000; // move to suspended state after 1s of being disconencted @@ -120,5 +124,6 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }); }; - return module.exports = helper.withTimeout(exports); + return module.exports = supportedBrowser() ? helper.withTimeout(exports) : {}; + }); From 97d0ec926e0c2179bf9a8ceaa4ece4c49ded73ae Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 13:16:59 +0100 Subject: [PATCH 11/14] Connection events test: restore old timeout defaults when done --- spec/browser/connection.test.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/spec/browser/connection.test.js b/spec/browser/connection.test.js index 184e7d7d6e..40e358cc52 100644 --- a/spec/browser/connection.test.js +++ b/spec/browser/connection.test.js @@ -5,7 +5,10 @@ define(['ably', 'shared_helper'], function(Ably, helper) { closeAndFinish = helper.closeAndFinish, monitorConnection = helper.monitorConnection, simulateDroppedConnection = helper.simulateDroppedConnection, - Defaults = Ably.Realtime.Defaults; + Defaults = Ably.Realtime.Defaults, + oldDisconnectTimeout = Defaults.disconnectTimeout, + oldSuspendedTimeout = Defaults.suspendedTimeout, + oldConnectTimeout = Defaults.connectTimeout; function supportedBrowser(test) { if(document.body.ononline === undefined) { @@ -86,6 +89,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { test.ok(new Date() - disconnectedAt < 250, 'Online event should have caused the connection to enter the connecting state immediately'); connection.once('connected', function() { test.ok(true, 'Successfully reconnected'); + Defaults.connectTimeout = oldConnectTimeout; closeAndFinish(test, realtime); }) }); @@ -97,7 +101,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { exports.device_going_online_causes_suspended_connection_to_reconnect_immediately = function(test) { Defaults.disconnectTimeout = 100; // retry connection more frequently - Defaults.suspendedTimeout = 1000; // move to suspended state after 1s of being disconencted + Defaults.suspendedTimeout = 1000; // move to suspended state after 1s of being disconnected var realtime = helper.AblyRealtime(), connection = realtime.connection, @@ -109,14 +113,16 @@ define(['ably', 'shared_helper'], function(Ably, helper) { test.expect(2); connection.on('failed', function () { test.ok(false, 'connection to server failed'); - closeAndFinish(test, realtime) + closeAndFinish(test, realtime); }); connection.once('suspended', function() { var suspendedAt = new Date(); test.ok(connection.state == 'suspended', 'Connection should still be suspended before we trigger it to connect'); connection.once('connecting', function() { - test.ok(new Date() - suspendedAt < 250, 'Online event should have caused the connection to enter the connecting state immediately'); + test.ok(new Date() - suspendedAt < 250, 'Online event should have caused the connection to enter the connecting state without waiting for suspended timeout'); + Defaults.disconnectTimeout = oldDisconnectTimeout; + Defaults.suspendedTimeout = oldSuspendedTimeout; closeAndFinish(test, realtime); }); // simulate online event @@ -124,6 +130,14 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }); }; + exports.clean99 = function(test) { + // Ensure defaults are reset + Defaults.connectTimeout = oldConnectTimeout; + Defaults.disconnectTimeout = oldDisconnectTimeout; + Defaults.suspendedTimeout = oldSuspendedTimeout; + test.done(); + } + return module.exports = supportedBrowser() ? helper.withTimeout(exports) : {}; }); From d99456ec92a08406fa1aa6251f10f05c66db14fd Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 14:41:56 +0100 Subject: [PATCH 12/14] More generous limits in connection test saucelabs is slow when running things concurrently --- spec/browser/connection.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/browser/connection.test.js b/spec/browser/connection.test.js index 40e358cc52..6153d1d5bc 100644 --- a/spec/browser/connection.test.js +++ b/spec/browser/connection.test.js @@ -51,10 +51,10 @@ define(['ably', 'shared_helper'], function(Ably, helper) { var connectedAt = new Date().getTime() connection.once('disconnected', function() { var disconnectedAt = new Date().getTime(); - test.ok(disconnectedAt - connectedAt < 250, 'Offline event caused connection to move to the disconnected state immediately (under 250ms)'); + test.ok(disconnectedAt - connectedAt < 1500, 'Offline event caused connection to move to the disconnected state'); connection.once('connecting', function() { var reconnectingAt = new Date().getTime(); - test.ok(reconnectingAt - disconnectedAt < 250, 'Client automatically reattempts connection even if the state is still offline'); + test.ok(reconnectingAt - disconnectedAt < 1500, 'Client automatically reattempts connection without waiting for disconnect timeout, even if the state is still offline'); connection.once('connected', function() { test.ok(true, 'Successfully reconnected'); closeAndFinish(test, realtime); @@ -86,7 +86,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { var disconnectedAt = new Date(); test.ok(connection.state == 'disconnected', 'Connection should still be disconnected before we trigger it to connect'); connection.once('connecting', function() { - test.ok(new Date() - disconnectedAt < 250, 'Online event should have caused the connection to enter the connecting state immediately'); + test.ok(new Date() - disconnectedAt < 1500, 'Online event should have caused the connection to enter the connecting state without waiting for disconnect timeout'); connection.once('connected', function() { test.ok(true, 'Successfully reconnected'); Defaults.connectTimeout = oldConnectTimeout; @@ -100,8 +100,8 @@ define(['ably', 'shared_helper'], function(Ably, helper) { }; exports.device_going_online_causes_suspended_connection_to_reconnect_immediately = function(test) { - Defaults.disconnectTimeout = 100; // retry connection more frequently - Defaults.suspendedTimeout = 1000; // move to suspended state after 1s of being disconnected + Defaults.disconnectTimeout = 200; // retry connection more frequently + Defaults.suspendedTimeout = 2000; // move to suspended state after 2s of being disconnected var realtime = helper.AblyRealtime(), connection = realtime.connection, @@ -120,7 +120,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { var suspendedAt = new Date(); test.ok(connection.state == 'suspended', 'Connection should still be suspended before we trigger it to connect'); connection.once('connecting', function() { - test.ok(new Date() - suspendedAt < 250, 'Online event should have caused the connection to enter the connecting state without waiting for suspended timeout'); + test.ok(new Date() - suspendedAt < 1500, 'Online event should have caused the connection to enter the connecting state without waiting for suspended timeout'); Defaults.disconnectTimeout = oldDisconnectTimeout; Defaults.suspendedTimeout = oldSuspendedTimeout; closeAndFinish(test, realtime); From 09d48b9df009687cb4ae1cc999c94ccc6b641317 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 16:11:20 +0100 Subject: [PATCH 13/14] Stats tests: request up to an hour ago to filter out data from fixtures --- spec/rest/stats.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/rest/stats.test.js b/spec/rest/stats.test.js index 5c26091a90..45ca0871c6 100644 --- a/spec/rest/stats.test.js +++ b/spec/rest/stats.test.js @@ -6,6 +6,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { startTime, intervalStart, timeOffset; var lastYear = new Date().getUTCFullYear() - 1; + var anHourAgo = new Date().valueOf() - 60 * 60 * 1000; // Set last interval to 3rd Feb 20xx 16:03:00, Javascript uses zero based months var firstIntervalEpoch = Date.UTC(lastYear, 1, 3, 15, 3, 0); @@ -57,6 +58,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { test.expect(1); rest.stats({ start: lastYear + '-02-03:15:03', + end: anHourAgo, direction: 'forwards' }, function(err, page) { if(err) { @@ -66,7 +68,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { } try { test.expect(3); - var stats = page.items; + var stats = page.items; test.equal(stats.length, 3, 'Verify 3 stat records found'); var totalInbound = 0, totalOutbound = 0; @@ -91,6 +93,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { test.expect(1); rest.stats({ start: firstIntervalEpoch, + end: anHourAgo, direction: 'forwards' }, function(err, page) { if(err) { @@ -100,7 +103,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { } try { test.expect(3); - var stats = page.items; + var stats = page.items; test.equal(stats.length, 3, 'Verify 3 stat records found'); var totalInbound = 0, totalOutbound = 0; @@ -125,6 +128,7 @@ define(['ably', 'shared_helper'], function(Ably, helper) { test.expect(1); rest.stats({ start: lastYear + '-02-03:15', + end: anHourAgo, direction: 'forwards', by: 'hour' }, function(err, page) { From e6c2362491ff9859ec7dee1a098921628229a81d Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 21 Sep 2015 16:40:24 +0100 Subject: [PATCH 14/14] Add ConnectionManager#abort (used by RealtimeChannel#onMessage) --- common/lib/transport/connectionmanager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lib/transport/connectionmanager.js b/common/lib/transport/connectionmanager.js index fb7459fa8b..628c2fad75 100644 --- a/common/lib/transport/connectionmanager.js +++ b/common/lib/transport/connectionmanager.js @@ -993,5 +993,9 @@ var ConnectionManager = (function() { this.ping(this.activeProtocol.getTransport(), onPingComplete); }; + ConnectionManager.prototype.abort = function(error) { + this.activeProtocol.getTransport().abort(error); + }; + return ConnectionManager; })();