From 423488adec5bf1a98a3e6d48ab6f93d540155472 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 5 Jun 2024 11:41:20 -0300 Subject: [PATCH] further --- docs/internal/private-api-usage.md | 5 ++ test/common/modules/private_api_recorder.js | 28 +++++-- test/realtime/auth.test.js | 88 +++++++++++++++++---- test/realtime/channel.test.js | 73 +++++++++++++++-- 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/docs/internal/private-api-usage.md b/docs/internal/private-api-usage.md index a1b9889e8..5eb95cf41 100644 --- a/docs/internal/private-api-usage.md +++ b/docs/internal/private-api-usage.md @@ -88,6 +88,8 @@ Marked in code. ### `test/realtime/channel.test.js` +Marked in code. + - `var createPM = Ably.protocolMessageFromDeserialized;` - `expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options');` (`channelOptions` isn’t public API) - listens for `channel._allChannelChanges.on(['update'],` @@ -101,7 +103,10 @@ Marked in code. ### `test/realtime/auth.test.js` +Marked in code. + - `var http = new Ably.Realtime._Http();` — just uses this as an HTTP client to fetch a JWT +- reads `realtime.auth.method` to check it’s `token` - spies on `rest.time` to count how many times called - checks `rest.serverTimeOffset` - spies on `transport.send` to look for an outgoing `AUTH` and check its properties diff --git a/test/common/modules/private_api_recorder.js b/test/common/modules/private_api_recorder.js index 069077c00..9992d522c 100644 --- a/test/common/modules/private_api_recorder.js +++ b/test/common/modules/private_api_recorder.js @@ -14,35 +14,47 @@ define([], function () { 'call.Platform.nextTick', 'call.PresenceMessage.fromValues', 'call.Utils.mixin', + 'call.Utils.toQueryString', 'call.channel.checkPendingState', 'call.channel.processMessage', + 'call.channel.requestState', 'call.channel.sendPresence', 'call.channel.sync', + 'call.connectionManager.onChannelMessage', + 'call.http.doUri', 'call.msgpack.decode', 'call.msgpack.encode', 'call.presence._myMembers.put', 'call.presence.waitSync', 'call.protocolMessageFromDeserialized', + 'call.realtime.connection.connectionManager.activeProtocol.getTransport', + 'call.transport.onProtocolMessage', 'call.transport.send', + 'listen.channel._allChannelChanges.attached', + 'listen.channel._allChannelChanges.update', 'listen.connectionManager.transport.active', 'listen.connectionManager.transport.pending', + 'pass.clientOption.webSocketConnectTimeout', 'read.Defaults.protocolVersion', 'read.EventEmitter.events', + 'read.auth.authOptions.authUrl', + 'read.auth.method', + 'read.auth.tokenParams.version', + 'read.channel.channelOptions', 'read.channel.channelOptions.cipher', 'read.connectionManager.connectionId', + 'read.realtime.connection.connectionManager.activeProtocol.transport', + 'read.rest.serverTimeOffset', 'replace.channel.attachImpl', + 'replace.channel.processMessage', + 'replace.channel.sendMessage', 'replace.channel.sendPresence', + 'replace.connectionManager.onChannelMessage', + 'replace.rest.time', + 'replace.transport.onProtocolMessage', 'replace.transport.send', - 'replace.channel.processMessage', 'write.channel._lastPayload', 'write.realtime.options.timeouts.realtimeRequestTimeout', - 'pass.clientOption.webSocketConnectTimeout', - 'read.realtime.connection.connectionManager.activeProtocol.transport', - 'replace.transport.onProtocolMessage', - 'call.transport.onProtocolMessage', - 'replace.connectionManager.onChannelMessage', - 'call.connectionManager.onChannelMessage', - 'call.channel.requestState', ]; class PrivateApiRecorder { diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 95e9e7112..28e7ce225 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -1,6 +1,12 @@ 'use strict'; -define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async, chai) { +define(['ably', 'shared_helper', 'async', 'chai', 'private_api_recorder'], function ( + Ably, + helper, + async, + chai, + privateApiRecorder, +) { var currentTime; var exampleTokenDetails; var exports = {}; @@ -20,8 +26,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* * Helper function to fetch JWT tokens from the echo server */ - function getJWT(params, callback) { + function getJWT(params, privateApiContext, callback) { var authUrl = echoServer + '/createJWT'; + privateApiContext.record('call.http.doUri'); whenPromiseSettles(http.doUri('get', authUrl, null, null, params), function (err, result) { if (result.error) { callback(result.error, null); @@ -162,6 +169,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Use authCallback for authentication with tokenRequest response */ it('auth_useAuthCallback_tokenRequestResponse', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime, rest = helper.AblyRest(); var authCallback = function (tokenParams, callback) { @@ -183,6 +192,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.on('connected', function () { try { + privateApiContext.record('read.auth.method'); expect(realtime.auth.method).to.equal('token'); closeAndFinish(done, realtime); } catch (err) { @@ -199,6 +209,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * to the auth callback */ it('auth_useAuthCallback_tokenDetailsResponse', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime, rest = helper.AblyRest(); var clientId = 'test clientid'; @@ -222,6 +234,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.on('connected', function () { try { + privateApiContext.record('read.auth.method'); expect(realtime.auth.method).to.equal('token'); closeAndFinish(done, realtime); } catch (err) { @@ -236,6 +249,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Use authCallback for authentication with token string response */ it('auth_useAuthCallback_tokenStringResponse', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime, rest = helper.AblyRest(); var authCallback = function (tokenParams, callback) { @@ -257,6 +272,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.on('connected', function () { try { + privateApiContext.record('read.auth.method'); expect(realtime.auth.method).to.equal('token'); closeAndFinish(done, realtime); } catch (err) { @@ -274,6 +290,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * take precedence */ it('auth_useAuthUrl_mixed_authParams_qsParams', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime, rest = helper.AblyRest(); whenPromiseSettles(rest.auth.createTokenRequest(null, null), function (err, tokenRequest) { @@ -294,6 +312,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async keyName: tokenRequest.keyName, mac: tokenRequest.mac, }; + privateApiContext.record('call.Utils.toQueryString'); var authPath = echoServer + '/qs_to_body' + utils.toQueryString(lowerPrecedenceTokenRequestParts); realtime = helper.AblyRealtime({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts }); @@ -647,7 +666,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * (when connecting with a token that expires while connected) */ testOnAllTransports('auth_token_expires', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var clientRealtime, rest = helper.AblyRest(); @@ -656,6 +675,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } + privateApiContext.record('call.Utils.mixin'); clientRealtime = helper.AblyRealtime(mixin(realtimeOpts, { tokenDetails: tokenDetails, queryTime: true })); clientRealtime.connection.on('failed', function () { @@ -683,17 +703,21 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and all subsequent requests use the time offset */ it('auth_query_time_once', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var rest = helper.AblyRest({ queryTime: true }), timeRequestCount = 0, originalTime = rest.time; /* stub time */ + privateApiContext.record('replace.rest.time'); rest.time = async function () { timeRequestCount += 1; return originalTime.call(rest); }; try { + privateApiContext.record('read.rest.serverTimeOffset'); expect( isNaN(parseInt(rest.serverTimeOffset)) && !rest.serverTimeOffset, 'Server time offset is empty and falsey until a time request has been made', @@ -710,6 +734,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (err) { return callback(err); } + privateApiContext.record('read.rest.serverTimeOffset'); expect( !isNaN(parseInt(rest.serverTimeOffset)), 'Server time offset is configured when time is requested', @@ -739,7 +764,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * new token */ testOnAllTransports('auth_tokenDetails_expiry_with_authcallback', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var realtime, rest = helper.AblyRest(); var clientId = 'test clientid'; @@ -754,6 +779,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; + privateApiContext.record('call.Utils.mixin'); realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { @@ -780,7 +806,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * token's expired */ testOnAllTransports('auth_token_string_expiry_with_authcallback', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var realtime, rest = helper.AblyRest(); var clientId = 'test clientid'; @@ -795,6 +821,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; + privateApiContext.record('call.Utils.mixin'); realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { @@ -820,7 +847,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Same as previous but with no way to generate a new token */ testOnAllTransports('auth_token_string_expiry_with_token', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var realtime, rest = helper.AblyRest(); var clientId = 'test clientid'; @@ -831,6 +858,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } + privateApiContext.record('call.Utils.mixin'); realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { @@ -861,7 +889,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Try to connect with an expired token string */ testOnAllTransports('auth_expired_token_string', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var realtime, rest = helper.AblyRest(); var clientId = 'test clientid'; @@ -871,6 +899,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } setTimeout(function () { + privateApiContext.record('call.Utils.mixin'); realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); realtime.connection.once('failed', function (stateChange) { try { @@ -900,7 +929,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * use authorize() to force a reauth using an existing authCallback */ testOnAllTransports.skip('reauth_authCallback', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var realtime, rest = helper.AblyRest(); var firstTime = true; @@ -917,6 +946,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; + privateApiContext.record('call.Utils.mixin'); realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback })); realtime.connection.once('connected', function () { var channel = realtime.channels.get('right'); @@ -954,6 +984,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA10j */ it('authorize_updates_stored_details', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime({ autoConnect: false, defaultTokenParams: { version: 1 }, @@ -962,6 +994,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); try { + privateApiContext.record('read.auth.tokenParams.version'); + privateApiContext.record('read.auth.authOptions.authUrl'); + expect(realtime.auth.tokenParams.version).to.equal(1, 'Check initial defaultTokenParams stored'); expect(realtime.auth.tokenDetails.token).to.equal('1', 'Check initial token stored'); expect(realtime.auth.authOptions.authUrl).to.equal('1', 'Check initial authUrl stored'); @@ -985,6 +1020,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Inject a fake AUTH message from realtime, check that we reauth and send our own in reply */ it('mocked_reauth', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var rest = helper.AblyRest(), authCallback = function (tokenParams, callback) { // Request a token (should happen twice) @@ -999,8 +1036,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime = helper.AblyRealtime({ authCallback: authCallback, transports: [helper.bestTransport] }); realtime.connection.once('connected', function () { + privateApiContext.record('read.realtime.connection.connectionManager.activeProtocol.transport'); var transport = realtime.connection.connectionManager.activeProtocol.transport, originalSend = transport.send; + privateApiContext.record('replace.transport.send'); /* Spy on transport.send to detect the outgoing AUTH */ transport.send = function (message) { if (message.action === 17) { @@ -1011,10 +1050,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); } } else { + privateApiContext.record('call.transport.send'); originalSend.call(this, message); } }; /* Inject a fake AUTH from realtime */ + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage({ action: 17 }); }); }); @@ -1024,12 +1065,15 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * has the requested clientId. */ it('auth_jwt_with_clientid', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var clientId = 'testJWTClientId'; + privateApiContext.record('call.Utils.mixin'); var params = mixin(keys, { clientId: clientId }); var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var realtime = helper.AblyRealtime({ authCallback: authCallback }); @@ -1057,12 +1101,15 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * has the requested clientId. Token will be returned with content-type application/jwt. */ it('auth_jwt_with_clientid_application_jwt', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret, returnType: 'jwt' }; var clientId = 'testJWTClientId'; + privateApiContext.record('call.Utils.mixin'); var params = mixin(keys, { clientId: clientId }); var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var realtime = helper.AblyRealtime({ authCallback: authCallback }); @@ -1090,10 +1137,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * to a channel fails. */ it('auth_jwt_with_subscribe_only_capability', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[3]; // get subscribe-only keys { "*":["subscribe"] } var params = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var realtime = helper.AblyRealtime({ authCallback: authCallback }); @@ -1117,10 +1166,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * to a channel succeeds. */ it('auth_jwt_with_publish_capability', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; var params = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var publishEvent = 'publishEvent', @@ -1146,10 +1197,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and receives the expected reason in the state change. */ it('auth_jwt_with_token_that_expires', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; var params = { keyName: currentKey.keyName, keySecret: currentKey.keySecret, expiresIn: 5 }; var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var realtime = helper.AblyRealtime({ authCallback: authCallback }); @@ -1171,12 +1224,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * without going through a disconnected state. */ it('auth_jwt_with_token_that_renews', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; // Sandbox sends an auth protocol message 30 seconds before a token expires. // We create a token that lasts 35 so there's room to receive the update event message. var params = { keyName: currentKey.keyName, keySecret: currentKey.keySecret, expiresIn: 35 }; var authCallback = function (tokenParams, callback) { - getJWT(params, callback); + getJWT(params, privateApiContext, callback); }; var realtime = helper.AblyRealtime({ authCallback: authCallback }); @@ -1199,9 +1254,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * verify it can make authenticated calls. */ it('init_client_with_simple_jwt_token', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var currentKey = helper.getTestApp().keys[0]; var params = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; - getJWT(params, function (err, token) { + getJWT(params, privateApiContext, function (err, token) { if (err) { done(err); return; @@ -1262,6 +1319,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } /* Fake an expired token */ + // TODO is _writing_ token.expires a private API? token.expires = Date.now() - 5000; var authCallbackCallCount = 0; var authCallback = function (_, callback) { diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index c339edef3..b101bf069 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -1,6 +1,12 @@ 'use strict'; -define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async, chai) { +define(['ably', 'shared_helper', 'async', 'chai', 'private_api_recorder'], function ( + Ably, + helper, + async, + chai, + privateApiRecorder, +) { var exports = {}; var _exports = {}; var expect = chai.expect; @@ -173,11 +179,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Channel init with options */ testOnAllTransports('channelinit0', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { try { var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { try { + privateApiContext.record('read.channel.channelOptions'); /* set options on init */ var channel0 = realtime.channels.get('channelinit0', { fakeOption: true }); expect(channel0.channelOptions.fakeOption).to.equal(true); @@ -489,7 +496,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); testOnAllTransports('attachWithChannelParamsBasicChannelsGet', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'attachWithChannelParamsBasicChannelsGet'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -508,6 +515,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { + privateApiContext.record('read.channel.channelOptions'); expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options'); expect(channel.params).to.deep.equal(params, 'Check result params'); expect(channel.modes).to.deep.equal(['subscribe'], 'Check result modes'); @@ -542,7 +550,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); testOnAllTransports('attachWithChannelParamsBasicSetOptions', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'attachWithChannelParamsBasicSetOptions'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -561,6 +569,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } + privateApiContext.record('read.channel.channelOptions'); expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options'); expect(channel.params).to.deep.equal(params, 'Check result params'); expect(channel.modes).to.deep.equal(['subscribe'], 'Check result modes'); @@ -663,7 +672,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); testOnAllTransports('setOptionsCallbackBehaviour', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'setOptionsCallbackBehaviour'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -682,6 +691,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { var channelUpdated = false; + privateApiContext.record('listen.channel._allChannelChanges.update'); channel._allChannelChanges.on(['update'], function () { channelUpdated = true; }); @@ -695,6 +705,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function () { /* Wait a tick so we don' depend on whether the update event runs the * channelUpdated listener or the setOptions listener first */ + privateApiContext.record('call.Platform.nextTicl'); Ably.Realtime.Platform.Config.nextTick(function () { expect( channelUpdated, @@ -707,6 +718,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { var channelUpdated = false; + privateApiContext.record('listen.channel._allChannelChanges.update'); + privateApiContext.record('listen.channel._allChannelChanges.attached'); channel._allChannelChanges.on(['attached', 'update'], function () { channelUpdated = true; }); @@ -716,6 +729,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async modes: ['subscribe'], }), function () { + privateApiContext.record('call.Platform.nextTick'); Ably.Realtime.Platform.Config.nextTick(function () { expect(channelUpdated, 'Check channel went to the server to update the channel mode').to.be.ok; cb(); @@ -738,7 +752,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Verify modes is ignored when params.modes is present */ testOnAllTransports('attachWithChannelParamsModesAndChannelModes', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'attachWithChannelParamsModesAndChannelModes'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -758,6 +772,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { + privateApiContext.record('read.channel.channelOptions'); expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options'); expect(channel.params).to.deep.equal(params, 'Check result params'); expect(channel.modes).to.deep.equal(paramsModes, 'Check result modes'); @@ -792,7 +807,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); testOnAllTransports('attachWithChannelModes', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'attachWithChannelModes'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -808,6 +823,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { + privateApiContext.record('read.channel.channelOptions'); expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options'); expect(channel.modes).to.deep.equal(modes, 'Check result modes'); } catch (err) { @@ -841,7 +857,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); testOnAllTransports('attachWithChannelParamsDeltaAndModes', function (realtimeOpts) { - return function (done) { + return function (done, privateApiContext) { var testName = 'attachWithChannelParamsDeltaAndModes'; try { var realtime = helper.AblyRealtime(realtimeOpts); @@ -858,6 +874,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { + privateApiContext.record('read.channel.channelOptions'); expect(channel.channelOptions).to.deep.equal(channelOptions, 'Check requested channel options'); expect(channel.params).to.deep.equal({ delta: 'vcdiff' }, 'Check result params'); expect(channel.modes).to.deep.equal(modes, 'Check result modes'); @@ -1112,6 +1129,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * immediate reattach. If that fails, it should go into suspended */ it('server_sent_detached', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1128,14 +1147,19 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Sabotage the reattach attempt, then simulate a server-sent detach */ + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function () {}; + privateApiContext.record('write.realtime.options.timeouts.realtimeRequestTimeout'); realtime.options.timeouts.realtimeRequestTimeout = 100; channel.once(function (stateChange) { expect(stateChange.current).to.equal('attaching', 'Channel reattach attempt happens immediately'); expect(stateChange.reason.code).to.equal(50000, 'check error is propogated in the reason'); cb(); }); + privateApiContext.record('call.realtime.connection.connectionManager.activeProtocol.getTransport'); var transport = realtime.connection.connectionManager.activeProtocol.getTransport(); + privateApiContext.record('call.protocolMessageFromDeserialized'); + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage( createPM({ action: 13, @@ -1163,13 +1187,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * result in the channel becoming suspended */ it('server_sent_detached_while_attaching', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached_while_attaching', channel = realtime.channels.get(channelName); realtime.connection.once('connected', function () { + privateApiContext.record('call.realtime.connection.connectionManager.activeProtocol.getTransport'); var transport = realtime.connection.connectionManager.activeProtocol.getTransport(); /* Mock sendMessage to respond to attaches with a DETACHED */ + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function (msg) { try { expect(msg.action).to.equal(10, 'check attach action'); @@ -1177,7 +1205,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } + privateApiContext.record('call.Platform.nextTick'); Ably.Realtime.Platform.Config.nextTick(function () { + privateApiContext.record('call.protocolMessageFromDeserialized'); + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage( createPM({ action: 13, @@ -1203,6 +1234,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * A server-sent ERROR, with channel field, should fail the channel */ it('server_sent_error', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_error', channel = realtime.channels.get(channelName); @@ -1222,7 +1255,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); } }); + privateApiContext.record('call.realtime.connection.connectionManager.activeProtocol.getTransport'); var transport = realtime.connection.connectionManager.activeProtocol.getTransport(); + privateApiContext.record('call.protocolMessageFromDeserialized'); + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage( createPM({ action: 9, @@ -1240,6 +1276,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * should emit an UPDATE event on the channel */ it('server_sent_attached_err', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime(), channelName = 'server_sent_attached_err', channel = realtime.channels.get(channelName); @@ -1264,7 +1302,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(channel.state).to.equal('attached', 'check channel still attached'); cb(); }); + privateApiContext.record('call.realtime.connection.connectionManager.activeProtocol.getTransport'); var transport = realtime.connection.connectionManager.activeProtocol.getTransport(); + privateApiContext.record('call.protocolMessageFromDeserialized'); + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage( createPM({ action: 11, @@ -1302,6 +1343,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * A channel attach that times out should be retried */ it('channel_attach_timeout', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + /* Use a fixed transport as attaches are resent when the transport changes */ var realtime = helper.AblyRealtime({ transports: [helper.bestTransport], @@ -1312,6 +1355,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = realtime.channels.get(channelName); /* Stub out the channel's ability to communicate */ + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function () {}; async.series( @@ -1331,6 +1375,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* nexttick so that it doesn't pick up the suspended event */ + privateApiContext.record('call.Platform.nextTick'); Ably.Realtime.Platform.Config.nextTick(function () { channel.once(function (stateChange) { expect(stateChange.current).to.equal('attaching', 'Check channel tries again after a bit'); @@ -1349,6 +1394,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check channel state implications of connection going into suspended */ it('suspended_connection', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + /* Use a fixed transport as attaches are resent when the transport changes */ /* Browsers throttle setTimeouts to min 1s in in active tabs; having timeouts less than that screws with the relative timings */ var realtime = helper.AblyRealtime({ @@ -1373,13 +1420,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Have the connection go into the suspended state, and check that the * channel goes into the suspended state and doesn't try to reattach * until the connection reconnects */ + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function (msg) { expect(false, 'Channel tried to send a message ' + JSON.stringify(msg)).to.be.ok; }; + privateApiContext.record('write.realtime.options.timeouts.realtimeRequestTimeout'); realtime.options.timeouts.realtimeRequestTimeout = 2000; helper.becomeSuspended(realtime, function () { /* nextTick as connection event is emitted before channel state is changed */ + privateApiContext.record('call.Platform.nextTick'); Ably.Realtime.Platform.Config.nextTick(function () { expect(channel.state).to.equal('suspended', 'check channel state is suspended'); cb(); @@ -1390,6 +1440,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.once(function (stateChange) { expect(stateChange.current).to.equal('connecting', 'Check we try to connect again'); /* We no longer want to fail the test for an attach, but still want to sabotage it */ + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function () {}; cb(); }); @@ -1419,6 +1470,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTL5i */ it('attached_while_detaching', function (done) { + const privateApiContext = privateApiRecorder.createContext(this); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1437,6 +1490,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Sabotage the detach attempt, detach, then simulate a server-sent attached while * the detach is ongoing. Expect to see the library reassert the detach */ let detachCount = 0; + privateApiContext.record('replace.channel.sendMessage'); channel.sendMessage = function (msg) { expect(msg.action).to.equal(12, 'Check we only see a detach. No attaches!'); expect(channel.state).to.equal('detaching', 'Check still in detaching state after both detaches'); @@ -1449,7 +1503,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* */ channel.detach(); setTimeout(function () { + privateApiContext.record('call.realtime.connection.connectionManager.activeProtocol.getTransport'); var transport = realtime.connection.connectionManager.activeProtocol.getTransport(); + privateApiContext.record('call.protocolMessageFromDeserialized'); + privateApiContext.record('call.transport.onProtocolMessage'); transport.onProtocolMessage(createPM({ action: 11, channel: channelName })); }, 0); },