From 1b3d4975997c917c83c3a1575fc5f5fe7254f046 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 20 Jun 2024 14:39:14 -0300 Subject: [PATCH 1/5] =?UTF-8?q?[UTS]=20Remove=20files=20that=20won?= =?UTF-8?q?=E2=80=99t=20be=20in=20the=20unified=20test=20suite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are the same files I didn’t add private API usage annotations to in 2dcf4ed. I’m doing this now so that when I make further UTS-related changes I don’t have to think about these files. --- test/browser/connection.test.js | 446 ------------ test/browser/http.test.js | 59 -- test/browser/modular.test.js | 1043 ----------------------------- test/browser/push.test.js | 93 --- test/browser/simple.test.js | 261 -------- test/mocha.html | 1 - test/playwright.html | 1 - test/realtime/transports.test.js | 259 ------- test/rest/bufferutils.test.js | 54 -- test/support/browser_file_list.js | 6 - test/support/browser_setup.js | 3 - 11 files changed, 2226 deletions(-) delete mode 100644 test/browser/connection.test.js delete mode 100644 test/browser/http.test.js delete mode 100644 test/browser/modular.test.js delete mode 100644 test/browser/push.test.js delete mode 100644 test/browser/simple.test.js delete mode 100644 test/realtime/transports.test.js delete mode 100644 test/rest/bufferutils.test.js diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js deleted file mode 100644 index 2c000b1488..0000000000 --- a/test/browser/connection.test.js +++ /dev/null @@ -1,446 +0,0 @@ -'use strict'; - -define(['shared_helper', 'chai'], function (Helper, chai) { - var { expect, assert } = chai; - var transportPreferenceName = 'ably-transport-preference'; - - function supportedBrowser() { - if (document.body.ononline === undefined) { - console.log('Online events not supported; skipping connection.test.js'); - return false; - } - - if (!window.WebSocket || !window.localStorage) { - console.log('Websockets or local storage not supported; skipping connection.test.js'); - return false; - } - - return true; - } - - function eraseSession(name) { - window.sessionStorage && window.sessionStorage.removeItem(name); - } - - if (supportedBrowser()) { - describe('browser/connection', function () { - this.timeout(60 * 1000); - - before(function (done) { - const helper = Helper.forHook(this); - helper.setupApp(function (err) { - if (err) { - done(err); - return; - } - done(); - }); - - /* Ensure session is clean for persistance tests */ - eraseSession('ably-connection-recovery'); - }); - - /** @spec RTN20a */ - it('device_going_offline_causes_disconnected_state', function (done) { - const helper = this.test.helper; - - var realtime = helper.AblyRealtime(), - connection = realtime.connection, - offlineEvent = new Event('offline', { bubbles: true }); - - helper.monitorConnection(done, realtime); - - connection.once('connected', function () { - var connectedAt = new Date().getTime(); - connection.once('disconnected', function () { - var disconnectedAt = new Date().getTime(); - try { - expect( - disconnectedAt - connectedAt < 1500, - 'Offline event caused connection to move to the disconnected state', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - connection.once('connecting', function () { - var reconnectingAt = new Date().getTime(); - try { - expect( - reconnectingAt - disconnectedAt < 1500, - 'Client automatically reattempts connection without waiting for disconnect timeout, even if the state is still offline', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - connection.once('connected', function () { - helper.closeAndFinish(done, realtime); - }); - }); - }); - - // simulate offline event, expect connection moves to disconnected state and waits to retry connection - document.dispatchEvent(offlineEvent); - }); - }); - - /** @spec RTN20b */ - it('device_going_online_causes_disconnected_connection_to_reconnect_immediately', function (done) { - const helper = this.test.helper; - - /* Give up trying to connect fairly quickly */ - var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 1000 }), - connection = realtime.connection, - onlineEvent = new Event('online', { bubbles: true }); - - helper.monitorConnection(done, realtime); - - // simulate the internet being failed by stubbing out tryATransport to foil - // the initial connection. (No immediate reconnect attempt since it was never - // connected in the first place) - var oldTransport = connection.connectionManager.tryATransport; - connection.connectionManager.tryATransport = function () {}; - - connection.once('disconnected', function () { - var disconnectedAt = new Date(); - try { - expect( - connection.state == 'disconnected', - 'Connection should still be disconnected before we trigger it to connect', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - connection.once('connecting', function () { - try { - expect( - new Date() - disconnectedAt < 1500, - 'Online event should have caused the connection to enter the connecting state without waiting for disconnect timeout', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - connection.once('connected', function () { - helper.closeAndFinish(done, realtime); - }); - }); - // restore the 'internet' and simulate an online event - connection.connectionManager.tryATransport = oldTransport; - document.dispatchEvent(onlineEvent); - }); - }); - - /** @spec RTN20b */ - it('device_going_online_causes_suspended_connection_to_reconnect_immediately', function (done) { - const helper = this.test.helper; - - /* move to suspended state after 2s of being disconnected */ - var realtime = helper.AblyRealtime({ - disconnectedRetryTimeout: 500, - realtimeRequestTimeout: 500, - connectionStateTtl: 2000, - }), - connection = realtime.connection, - onlineEvent = new Event('online', { bubbles: true }); - - // Easiest way to have all transports attempt fail it to stub out tryATransport - connection.connectionManager.tryATransport = function () {}; - - connection.on('failed', function () { - helper.closeAndFinish(done, realtime, new Error('connection to server failed')); - }); - - connection.once('suspended', function () { - var suspendedAt = new Date(); - try { - expect( - connection.state == 'suspended', - 'Connection should still be suspended before we trigger it to connect', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - connection.once('connecting', function () { - try { - expect( - new Date() - suspendedAt < 1500, - 'Online event should have caused the connection to enter the connecting state without waiting for suspended timeout', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - helper.closeAndFinish(done, realtime); - }); - // simulate online event - document.dispatchEvent(onlineEvent); - }); - }); - - /** @spec RTN20c */ - it('device_going_online_causes_connecting_connection_to_retry_attempt', function (done) { - const helper = this.test.helper; - - var realtime = helper.AblyRealtime({}), - connection = realtime.connection, - onlineEvent = new Event('online', { bubbles: true }), - oldTransport, - newTransport; - - helper.monitorConnection(done, realtime, ['failed', 'disconnected', 'suspended']); - - /* Sabotage the connection attempt by emitting onlineEvent when transport is pending */ - connection.connectionManager.once('transport.pending', function (transport) { - oldTransport = transport; - document.dispatchEvent(onlineEvent); - - connection.connectionManager.once('transport.pending', function (transport) { - newTransport = transport; - }); - }); - - connection.on('connected', function () { - /* Ensure that the original pending transport has been disposed of and superseded */ - expect(oldTransport.isDisposed).to.be.ok; - expect(newTransport.isDisposed).to.not.be.ok; - expect(oldTransport).to.not.equal(newTransport); - expect(realtime.connection.connectionManager.activeProtocol.transport).to.equal(newTransport); - helper.closeAndFinish(done, realtime); - }); - }); - - /* uses internal realtime knowledge of the format of the connection key to - * check if a connection key is the result of a successful recovery of another */ - function sameConnection(keyA, keyB) { - return keyA.split('-')[0] === keyB.split('-')[0]; - } - - /** - * @specpartial RTN16 test - * @specpartial RTN16d - */ - it('page_refresh_with_recovery', function (done) { - const helper = this.test.helper; - - var realtimeOpts = { - recover: function (lastConnectionDetails, cb) { - cb(true); - }, - }, - realtime = helper.AblyRealtime(realtimeOpts), - refreshEvent = new Event('beforeunload', { bubbles: true }); - - helper.monitorConnection(done, realtime); - - realtime.connection.once('connected', function () { - var connectionKey = realtime.connection.key; - document.dispatchEvent(refreshEvent); - try { - expect(realtime.connection.state).to.equal( - 'connected', - 'check connection state initially unaffected by page refresh', - ); - helper.simulateDroppedConnection(realtime); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - - var newRealtime = helper.AblyRealtime(realtimeOpts); - newRealtime.connection.once('connected', function () { - try { - expect( - sameConnection(connectionKey, newRealtime.connection.key), - 'Check new realtime recovered the connection from the cookie', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, [realtime, newRealtime], err); - return; - } - helper.closeAndFinish(done, [realtime, newRealtime]); - }); - }); - }); - - /** @nospec */ - it('page_refresh_persist_with_denied_recovery', function (done) { - const helper = this.test.helper; - - var realtimeOpts = { - recover: function (lastConnectionDetails, cb) { - cb(false); - }, - }; - var realtime = helper.AblyRealtime(realtimeOpts), - refreshEvent = new Event('beforeunload', { bubbles: true }); - - helper.monitorConnection(done, realtime); - - realtime.connection.once('connected', function () { - var connectionKey = realtime.connection.key; - document.dispatchEvent(refreshEvent); - try { - expect(realtime.connection.state).to.equal( - 'connected', - 'check connection state initially unaffected by page refresh', - ); - helper.simulateDroppedConnection(realtime); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - - var newRealtime = helper.AblyRealtime(realtimeOpts); - newRealtime.connection.once('connected', function () { - try { - expect( - !sameConnection(connectionKey, newRealtime.connection.key), - 'Check new realtime created a new connection', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, [realtime, newRealtime], err); - return; - } - helper.closeAndFinish(done, [realtime, newRealtime]); - }); - helper.monitorConnection(done, newRealtime); - }); - }); - - /** @nospec */ - it('page_refresh_with_close_on_unload', function (done) { - var realtime = this.test.helper.AblyRealtime({ closeOnUnload: true }), - refreshEvent = new Event('beforeunload', { bubbles: true }); - - this.test.helper.monitorConnection(done, realtime); - - realtime.connection.once('connected', function () { - try { - var connectionKey = realtime.connection.key; - document.dispatchEvent(refreshEvent); - var state = realtime.connection.state; - expect(state == 'closing' || state == 'closed', 'check page refresh triggered a close').to.be.ok; - } catch (err) { - done(err); - return; - } - done(); - }); - }); - - /** - * @specpartial RTN16 - * @specpartial RTN16d - */ - it('page_refresh_with_manual_recovery', function (done) { - const helper = this.test.helper; - - var realtime = helper.AblyRealtime({ closeOnUnload: false }), - refreshEvent = new Event('beforeunload', { bubbles: true }); - - helper.monitorConnection(done, realtime); - - realtime.connection.once('connected', function () { - var connectionKey = realtime.connection.key, - recoveryKey = realtime.connection.recoveryKey; - - document.dispatchEvent(refreshEvent); - try { - expect(realtime.connection.state).to.equal( - 'connected', - 'check connection state initially unaffected by page refresh', - ); - helper.simulateDroppedConnection(realtime); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - - var newRealtime = helper.AblyRealtime({ recover: recoveryKey }); - newRealtime.connection.once('connected', function () { - try { - expect( - sameConnection(connectionKey, newRealtime.connection.key), - 'Check new realtime recovered the old', - ).to.be.ok; - } catch (err) { - helper.closeAndFinish(done, [realtime, newRealtime], err); - return; - } - helper.closeAndFinish(done, [realtime, newRealtime]); - }); - }); - }); - - /** @nospec */ - it('page_refresh_with_multiple_recovery_scopes', async function () { - const realtimeOpts = { recover: (_, cb) => cb(true) }, - opts1 = Object.assign({ recoveryKeyStorageName: 'recovery-1' }, realtimeOpts), - opts2 = Object.assign({ recoveryKeyStorageName: 'recovery-2' }, realtimeOpts), - realtime1 = this.test.helper.AblyRealtime(opts1), - realtime2 = this.test.helper.AblyRealtime(opts2), - refreshEvent = new Event('beforeunload', { bubbles: true }); - - await Promise.all([realtime1.connection.once('connected'), realtime2.connection.once('connected')]); - const connId1 = realtime1.connection.id; - const connId2 = realtime2.connection.id; - - document.dispatchEvent(refreshEvent); - - this.test.helper.simulateDroppedConnection(realtime1); - this.test.helper.simulateDroppedConnection(realtime2); - - await new Promise((res) => setTimeout(res, 1000)); - - const newRealtime1 = this.test.helper.AblyRealtime(opts1); - const newRealtime2 = this.test.helper.AblyRealtime(opts2); - await Promise.all([newRealtime1.connection.once('connected'), newRealtime2.connection.once('connected')]); - assert.equal(connId1, newRealtime1.connection.id); - assert.equal(connId2, newRealtime2.connection.id); - - await Promise.all( - [realtime1, realtime2, newRealtime1, newRealtime2].map((rt) => this.test.helper.closeAndFinishAsync(rt)), - ); - }); - - /** @nospec */ - it('persist_preferred_transport', function (done) { - const helper = this.test.helper; - - var realtime = helper.AblyRealtime(); - - realtime.connection.connectionManager.on(function (transport) { - if (this.event === 'transport.active' && transport.shortName === 'web_socket') { - try { - expect(window.localStorage.getItem(transportPreferenceName)).to.equal( - JSON.stringify({ value: 'web_socket' }), - ); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - helper.closeAndFinish(done, realtime); - } - }); - helper.monitorConnection(done, realtime); - }); - - /** @nospec */ - it('browser_transports', function (done) { - var realtime = this.test.helper.AblyRealtime(); - try { - expect(realtime.connection.connectionManager.baseTransport).to.equal('xhr_polling'); - expect(realtime.connection.connectionManager.webSocketTransportAvailable).to.be.ok; - } catch (err) { - this.test.helper.closeAndFinish(done, realtime, err); - return; - } - this.test.helper.closeAndFinish(done, realtime); - }); - }); - } -}); diff --git a/test/browser/http.test.js b/test/browser/http.test.js deleted file mode 100644 index c84f909c26..0000000000 --- a/test/browser/http.test.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { - var rest; - var expect = chai.expect; - - describe('rest/http/fetch', function () { - this.timeout(60 * 1000); - let initialXhrSupported; - before(function (done) { - const helper = Helper.forHook(this); - initialXhrSupported = Ably.Rest.Platform.Config.xhrSupported; - Ably.Rest.Platform.Config.xhrSupported = false; - helper.setupApp(function () { - rest = helper.AblyRest(); - done(); - }); - }); - - after((done) => { - Ably.Rest.Platform.Config.xhrSupported = initialXhrSupported; - done(); - }); - - /** @nospec */ - it('Should use fetch when XHR is not supported', function (done) { - let oldFetch = window.fetch; - window.fetch = () => { - done(); - window.fetch = oldFetch; - }; - const channel = rest.channels.get('http_test_channel'); - channel.publish('test', 'Testing fetch support'); - }); - - /** @nospec */ - it('Should succeed in using fetch to publish a message', function (done) { - const channel = rest.channels.get('http_test_channel'); - Helper.whenPromiseSettles(channel.publish('test', 'Testing fetch support'), (err) => { - expect(err).to.not.exist; - done(); - }); - }); - - /** - * RTL6b talks about a callback which should receive an error object (which what we're doing here), but suggests to test other things. - * This test simply tests that with fetch API we're still receiving an error, so it's probably @nospec. - * - * @nospec - */ - it('Should pass errors correctly', function (done) { - const channel = rest.channels.get(''); - Helper.whenPromiseSettles(channel.publish('test', 'Invalid message'), (err) => { - expect(err).to.exist; - done(); - }); - }); - }); -}); diff --git a/test/browser/modular.test.js b/test/browser/modular.test.js deleted file mode 100644 index 128e69aaad..0000000000 --- a/test/browser/modular.test.js +++ /dev/null @@ -1,1043 +0,0 @@ -import { - BaseRest, - BaseRealtime, - Rest, - generateRandomKey, - getDefaultCryptoParams, - decodeMessage, - decodeEncryptedMessage, - decodeMessages, - decodeEncryptedMessages, - Crypto, - MsgPack, - RealtimePresence, - decodePresenceMessage, - decodePresenceMessages, - constructPresenceMessage, - XHRPolling, - WebSocketTransport, - FetchRequest, - XHRRequest, - MessageInteractions, -} from '../../build/modular/index.mjs'; - -function registerAblyModularTests(Helper) { - describe('browser/modular', function () { - this.timeout(10 * 1000); - const expect = chai.expect; - const BufferUtils = BaseRest.Platform.BufferUtils; - const loadTestData = async (helper, dataPath) => { - return new Promise((resolve, reject) => { - helper.loadTestData(dataPath, (err, testData) => (err ? reject(err) : resolve(testData))); - }); - }; - - async function monitorConnectionThenCloseAndFinish(helper, action, realtime, states) { - try { - await helper.monitorConnectionAsync(action, realtime, states); - } finally { - await helper.closeAndFinishAsync(realtime); - } - } - - before(function (done) { - const helper = Helper.forHook(this); - helper.setupApp(done); - }); - - describe('attempting to initialize with no client options', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - /** @nospec */ - it('throws an error', () => { - expect(() => new clientClass()).to.throw('must be initialized with a client options object'); - }); - }); - } - }); - - describe('attempting to initialize with just an API key', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - /** @nospec */ - it('throws an error', () => { - expect(() => new clientClass('foo:bar')).to.throw( - 'cannot be initialized with just an Ably API key; you must provide a client options object with a `plugins` property', - ); - }); - }); - } - }); - - describe('attempting to initialize with just a token', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - /** @nospec */ - it('throws an error', () => { - expect(() => new clientClass('foo')).to.throw( - 'cannot be initialized with just an Ably Token; you must provide a client options object with a `plugins` property', - ); - }); - }); - } - }); - - describe('without any plugins', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, function () { - /** @nospec */ - it('throws an error due to the absence of an HTTP plugin', function () { - expect(() => new clientClass(this.test.helper.ablyClientOptions())).to.throw( - 'No HTTP request plugin provided. Provide at least one of the FetchRequest or XHRRequest plugins.', - ); - }); - }); - } - }); - - describe('Rest', () => { - const restScenarios = [ - { - description: 'use push admin functionality', - action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), - }, - { description: 'call `time()`', action: (client) => client.time() }, - { - description: 'call `auth.createTokenRequest()` with `queryTime` option enabled', - action: (client, helper) => - client.auth.createTokenRequest(undefined, { - key: helper.getTestApp().keys[0].keyStr /* if passing authOptions you have to explicitly pass the key */, - queryTime: true, - }), - }, - { description: 'call `stats()`', action: (client) => client.stats() }, - { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, - { - description: 'call `batchPublish(...)`', - action: (client) => client.batchPublish({ channels: ['channel'], messages: { data: { foo: 'bar' } } }), - }, - { - description: 'call `batchPresence(...)`', - action: (client) => client.batchPresence(['channel']), - }, - { - description: 'call `auth.revokeTokens(...)`', - getAdditionalClientOptions: (helper) => { - const testApp = helper.getTestApp(); - return { key: testApp.keys[4].keyStr /* this key has revocableTokens enabled */ }; - }, - action: (client) => client.auth.revokeTokens([{ type: 'clientId', value: 'foo' }]), - }, - { - description: 'call channel’s `history()`', - action: (client) => client.channels.get('channel').history(), - }, - { - description: 'call channel’s `presence.history()`', - additionalRealtimePlugins: { RealtimePresence }, - action: (client) => client.channels.get('channel').presence.history(), - }, - { - description: 'call channel’s `status()`', - action: (client) => client.channels.get('channel').status(), - }, - ]; - - describe('BaseRest without explicit Rest', () => { - for (const scenario of restScenarios) { - /** @nospec */ - it(`allows you to ${scenario.description}`, async function () { - const helper = this.test.helper; - const client = new BaseRest( - helper.ablyClientOptions({ ...scenario.getAdditionalClientOptions?.(helper), plugins: { FetchRequest } }), - ); - - let thrownError = null; - try { - await scenario.action(client, helper); - } catch (error) { - thrownError = error; - } - - expect(thrownError).to.be.null; - }); - } - }); - - describe('BaseRealtime with Rest', () => { - for (const scenario of restScenarios) { - /** @nospec */ - it(`allows you to ${scenario.description}`, async function () { - const helper = this.test.helper; - const client = new BaseRealtime( - helper.ablyClientOptions({ - autoConnect: false, - ...scenario.getAdditionalClientOptions?.(helper), - plugins: { - WebSocketTransport, - FetchRequest, - Rest, - ...scenario.additionalRealtimePlugins, - }, - }), - ); - - let thrownError = null; - try { - await scenario.action(client, helper); - } catch (error) { - thrownError = error; - } - - expect(thrownError).to.be.null; - }); - } - }); - - describe('BaseRealtime without Rest', () => { - /** @nospec */ - it('still allows publishing and subscribing', async function () { - const helper = this.test.helper; - const client = new BaseRealtime( - this.test.helper.ablyClientOptions({ plugins: { WebSocketTransport, FetchRequest } }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const channel = client.channels.get('channel'); - await channel.attach(); - - const recievedMessagePromise = new Promise((resolve) => { - channel.subscribe((message) => { - resolve(message); - }); - }); - - await channel.publish({ data: { foo: 'bar' } }); - - const receivedMessage = await recievedMessagePromise; - expect(receivedMessage.data).to.eql({ foo: 'bar' }); - }, - client, - ); - }); - - /** @nospec */ - it('allows `auth.createTokenRequest()` without `queryTime` option enabled', async function () { - const client = new BaseRealtime( - this.test.helper.ablyClientOptions({ autoConnect: false, plugins: { WebSocketTransport, FetchRequest } }), - ); - - const tokenRequest = await client.auth.createTokenRequest(); - expect(tokenRequest).to.be.an('object'); - }); - - for (const scenario of restScenarios) { - /** @nospec */ - it(`throws an error when attempting to ${scenario.description}`, async function () { - const helper = this.test.helper; - const client = new BaseRealtime( - this.test.helper.ablyClientOptions({ - autoConnect: false, - ...scenario.getAdditionalClientOptions?.(helper), - plugins: { - WebSocketTransport, - FetchRequest, - ...scenario.additionalRealtimePlugins, - }, - }), - ); - - let thrownError = null; - try { - await scenario.action(client, helper); - } catch (error) { - thrownError = error; - } - - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Rest plugin not provided'); - }); - } - }); - }); - - describe('Crypto standalone functions', () => { - /** - * This test does a basic sanity check for RSE2b but for a function exposed in the modular bundle, - * so it's not really relevant to the spec item here. - * - * @nospec - */ - it('generateRandomKey', async () => { - const key = await generateRandomKey(); - expect(key).to.be.an('ArrayBuffer'); - }); - - /** @nospec */ - it('getDefaultCryptoParams', async () => { - const key = await generateRandomKey(); - const params = getDefaultCryptoParams({ key }); - expect(params).to.be.an('object'); - }); - }); - - describe('Message standalone functions', () => { - async function testDecodesMessageData(helper, functionUnderTest) { - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - - const item = testData.items[1]; - const decoded = await functionUnderTest(item.encoded); - - expect(decoded.data).to.be.an('ArrayBuffer'); - } - - describe('decodeMessage', () => { - /** - * This tests TM3 (fromEncoded part) but for a function exposed in the modular bundle, - * so it's not really relevant to the spec item. - * - * @nospec - */ - it('decodes a message’s data', async function () { - testDecodesMessageData(this.test.helper, decodeMessage); - }); - - /** @nospec */ - it('throws an error when given channel options with a cipher', async function () { - const helper = this.test.helper; - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - let thrownError = null; - try { - await decodeMessage(testData.items[0].encrypted, { cipher: { key, iv } }); - } catch (error) { - thrownError = error; - } - - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Crypto plugin not provided'); - }); - }); - - describe('decodeEncryptedMessage', async () => { - /** @nospec */ - it('decodes a message’s data', async function () { - testDecodesMessageData(this.test.helper, decodeEncryptedMessage); - }); - - /** @nospec */ - it('decrypts a message', async function () { - const helper = this.test.helper; - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - for (const item of testData.items) { - const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ - decodeMessage(item.encoded), - decodeEncryptedMessage(item.encrypted, { cipher: { key, iv } }), - ]); - - this.test.helper.testMessageEquality(decodedFromEncoded, decodedFromEncrypted); - } - }); - }); - - async function testDecodesMessagesData(helper, functionUnderTest) { - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - - const items = [testData.items[1], testData.items[3]]; - const decoded = await functionUnderTest(items.map((item) => item.encoded)); - - expect(decoded[0].data).to.be.an('ArrayBuffer'); - expect(decoded[1].data).to.be.an('array'); - } - - describe('decodeMessages', () => { - /** - * This tests TM3 (fromEncodedArray part) but for a function exposed in the modular bundle, - * so it's not really relevant to the spec item. - * - * @nospec - */ - it('decodes messages’ data', async function () { - testDecodesMessagesData(this.test.helper, decodeMessages); - }); - - /** @nospec */ - it('throws an error when given channel options with a cipher', async function () { - const helper = this.test.helper; - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - let thrownError = null; - try { - await decodeMessages( - testData.items.map((item) => item.encrypted), - { cipher: { key, iv } }, - ); - } catch (error) { - thrownError = error; - } - - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Crypto plugin not provided'); - }); - }); - - describe('decodeEncryptedMessages', () => { - /** @nospec */ - it('decodes messages’ data', async function () { - testDecodesMessagesData(this.test.helper, decodeEncryptedMessages); - }); - - /** @nospec */ - it('decrypts messages', async function () { - const helper = this.test.helper; - const testData = await loadTestData(helper, helper.testResourcesPath + 'crypto-data-128.json'); - - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ - decodeMessages(testData.items.map((item) => item.encoded)), - decodeEncryptedMessages( - testData.items.map((item) => item.encrypted), - { cipher: { key, iv } }, - ), - ]); - - for (let i = 0; i < decodedFromEncoded.length; i++) { - this.test.helper.testMessageEquality(decodedFromEncoded[i], decodedFromEncrypted[i]); - } - }); - }); - }); - - describe('Crypto', () => { - describe('without Crypto', () => { - async function testThrowsAnErrorWhenGivenChannelOptionsWithACipher(helper, clientClassConfig) { - const client = new clientClassConfig.clientClass( - helper.ablyClientOptions({ - ...clientClassConfig.additionalClientOptions, - plugins: { - ...clientClassConfig.additionalPlugins, - FetchRequest, - }, - }), - ); - const key = await generateRandomKey(); - expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto plugin not provided'); - } - - for (const clientClassConfig of [ - { clientClass: BaseRest }, - { - clientClass: BaseRealtime, - additionalClientOptions: { autoConnect: false }, - additionalPlugins: { WebSocketTransport }, - }, - ]) { - describe(clientClassConfig.clientClass.name, () => { - /** @nospec */ - it('throws an error when given channel options with a cipher', async function () { - await testThrowsAnErrorWhenGivenChannelOptionsWithACipher(this.test.helper, clientClassConfig); - }); - }); - } - }); - - describe('with Crypto', () => { - async function testIsAbleToPublishEncryptedMessages(helper, clientClassConfig) { - const clientOptions = helper.ablyClientOptions(); - - const key = await generateRandomKey(); - - // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption - - const rxClient = new BaseRealtime({ ...clientOptions, plugins: { WebSocketTransport, FetchRequest } }); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const rxChannel = rxClient.channels.get('channel'); - await rxChannel.attach(); - - const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); - - const encryptionChannelOptions = { cipher: { key } }; - - const txMessage = { name: 'message', data: 'data' }; - const txClient = new clientClassConfig.clientClass({ - ...clientOptions, - plugins: { - ...clientClassConfig.additionalPlugins, - FetchRequest, - Crypto, - }, - }); - - await (clientClassConfig.isRealtime - ? monitorConnectionThenCloseAndFinish - : async (helper, op) => await op())( - helper, - async () => { - const txChannel = txClient.channels.get('channel', encryptionChannelOptions); - await txChannel.publish(txMessage); - - const rxMessage = await rxMessagePromise; - - // Verify that the message was published with encryption - expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); - - // Verify that the message was correctly encrypted - const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); - helper.testMessageEquality(rxMessageDecrypted, txMessage); - }, - txClient, - ); - }, - rxClient, - ); - } - - for (const clientClassConfig of [ - { clientClass: BaseRest, isRealtime: false }, - { - clientClass: BaseRealtime, - additionalPlugins: { WebSocketTransport }, - isRealtime: true, - }, - ]) { - describe(clientClassConfig.clientClass.name, () => { - /** @nospec */ - it('is able to publish encrypted messages', async function () { - await testIsAbleToPublishEncryptedMessages(this.test.helper, clientClassConfig); - }); - }); - } - }); - }); - - describe('MsgPack', () => { - async function testRestUsesContentType(rest, expectedContentType) { - const channelName = 'channel'; - const channel = rest.channels.get(channelName); - const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { - rest.http.do = async (method, path, headers, body, params) => { - if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { - return new Promise(() => {}); - } - resolve(headers['content-type']); - return { error: null }; - }; - }); - - await channel.publish('message', 'body'); - - const contentTypeUsedForPublish = await contentTypeUsedForPublishPromise; - expect(contentTypeUsedForPublish).to.equal(expectedContentType); - } - - async function testRealtimeUsesFormat(realtime, expectedFormat) { - const formatUsedForConnectionPromise = new Promise((resolve, reject) => { - realtime.connection.connectionManager.connectImpl = (transportParams) => { - resolve(transportParams.format); - }; - }); - realtime.connect(); - - const formatUsedForConnection = await formatUsedForConnectionPromise; - expect(formatUsedForConnection).to.equal(expectedFormat); - } - - // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified - describe('with useBinaryProtocol client option', () => { - describe('without MsgPack', () => { - describe('BaseRest', () => { - /** @nospec */ - it('uses JSON', async function () { - const client = new BaseRest( - this.test.helper.ablyClientOptions({ useBinaryProtocol: true, plugins: { FetchRequest } }), - ); - await testRestUsesContentType(client, 'application/json'); - }); - }); - - describe('BaseRealtime', () => { - /** @nospec */ - it('uses JSON', async function () { - const helper = this.test.helper; - const client = new BaseRealtime( - helper.ablyClientOptions({ - useBinaryProtocol: true, - autoConnect: false, - plugins: { - WebSocketTransport, - FetchRequest, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - await testRealtimeUsesFormat(client, 'json'); - }, - client, - ); - }); - }); - }); - - describe('with MsgPack', () => { - describe('BaseRest', () => { - /** @nospec */ - it('uses MessagePack', async function () { - const client = new BaseRest( - this.test.helper.ablyClientOptions({ - useBinaryProtocol: true, - plugins: { - FetchRequest, - MsgPack, - }, - }), - ); - await testRestUsesContentType(client, 'application/x-msgpack'); - }); - }); - - describe('BaseRealtime', () => { - /** @nospec */ - it('uses MessagePack', async function () { - const helper = this.test.helper; - const client = new BaseRealtime( - helper.ablyClientOptions({ - useBinaryProtocol: true, - autoConnect: false, - plugins: { - WebSocketTransport, - FetchRequest, - MsgPack, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - await testRealtimeUsesFormat(client, 'msgpack'); - }, - client, - ); - }); - }); - }); - }); - }); - - describe('RealtimePresence', () => { - describe('BaseRealtime without RealtimePresence', () => { - /** @nospec */ - it('throws an error when attempting to access the `presence` property', async function () { - const helper = this.test.helper; - const client = new BaseRealtime(helper.ablyClientOptions({ plugins: { WebSocketTransport, FetchRequest } })); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const channel = client.channels.get('channel'); - - expect(() => channel.presence).to.throw('RealtimePresence plugin not provided'); - }, - client, - ); - }); - - /** @nospec */ - it('doesn’t break when it receives a PRESENCE ProtocolMessage', async function () { - const helper = this.test.helper; - const rxClient = new BaseRealtime( - helper.ablyClientOptions({ plugins: { WebSocketTransport, FetchRequest } }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const rxChannel = rxClient.channels.get('channel'); - - await rxChannel.attach(); - - const receivedMessagePromise = new Promise((resolve) => rxChannel.subscribe(resolve)); - - const txClient = new BaseRealtime( - this.test.helper.ablyClientOptions({ - clientId: Helper.randomString(), - plugins: { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const txChannel = txClient.channels.get('channel'); - - await txChannel.publish('message', 'body'); - await txChannel.presence.enter(); - - // The idea being here that in order for receivedMessagePromise to resolve, rxClient must have first processed the PRESENCE ProtocolMessage that resulted from txChannel.presence.enter() - - await receivedMessagePromise; - }, - txClient, - ); - }, - rxClient, - ); - }); - }); - - describe('BaseRealtime with RealtimePresence', () => { - /** - * Tests RTP6b and RTP8a but for a RealtimePresence plugin with modular BaseRealtime, - * so it's not explictly related to any spec item. - * - * @nospec - */ - it('offers realtime presence functionality', async function () { - const helper = this.test.helper; - const rxClient = new BaseRealtime( - helper.ablyClientOptions({ - plugins: { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }, - }), - ); - const rxChannel = rxClient.channels.get('channel'); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const txClientId = Helper.randomString(); - const txClient = new BaseRealtime( - this.test.helper.ablyClientOptions({ - clientId: txClientId, - plugins: { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const txChannel = txClient.channels.get('channel'); - - let resolveRxPresenceMessagePromise; - const rxPresenceMessagePromise = new Promise((resolve, reject) => { - resolveRxPresenceMessagePromise = resolve; - }); - await rxChannel.presence.subscribe('enter', resolveRxPresenceMessagePromise); - await txChannel.presence.enter(); - - const rxPresenceMessage = await rxPresenceMessagePromise; - expect(rxPresenceMessage.clientId).to.equal(txClientId); - }, - txClient, - ); - }, - rxClient, - ); - }); - }); - }); - - describe('PresenceMessage standalone functions', () => { - describe('decodePresenceMessage', () => { - /** - * Tests TP4 (fromEncoded part) but for a function exposed in the modular bundle, - * so it's not really relevant to the spec item. - * - * @nospec - */ - it('decodes a presence message’s data', async () => { - const buffer = BufferUtils.utf8Encode('foo'); - const encodedMessage = { data: BufferUtils.base64Encode(buffer), encoding: 'base64' }; - - const decodedMessage = await decodePresenceMessage(encodedMessage); - - expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffer)).to.be.true; - expect(decodedMessage.encoding).to.be.null; - }); - }); - - describe('decodeMessages', () => { - /** - * Tests TP4 (fromEncodedArray part) but for a function exposed in the modular bundle, - * so it's not really relevant to the spec item. - * - * @nospec - */ - it('decodes presence messages’ data', async () => { - const buffers = ['foo', 'bar'].map((data) => BufferUtils.utf8Encode(data)); - const encodedMessages = buffers.map((buffer) => ({ - data: BufferUtils.base64Encode(buffer), - encoding: 'base64', - })); - - const decodedMessages = await decodePresenceMessages(encodedMessages); - - for (let i = 0; i < decodedMessages.length; i++) { - const decodedMessage = decodedMessages[i]; - - expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffers[i])).to.be.true; - expect(decodedMessage.encoding).to.be.null; - } - }); - }); - - describe('constructPresenceMessage', () => { - /** @nospec */ - it('creates a PresenceMessage instance', async () => { - const extras = { foo: 'bar' }; - const presenceMessage = constructPresenceMessage({ extras }); - - expect(presenceMessage.constructor.name).to.contain('PresenceMessage'); - expect(presenceMessage.extras).to.equal(extras); - }); - }); - }); - - describe('Transports', () => { - describe('BaseRealtime', () => { - describe('without a transport plugin', () => { - /** @nospec */ - it('throws an error due to absence of a transport plugin', function () { - expect(() => new BaseRealtime(this.test.helper.ablyClientOptions({ plugins: { FetchRequest } }))).to.throw( - 'no requested transports available', - ); - }); - }); - - for (const scenario of [ - { pluginsKey: 'WebSocketTransport', transportPlugin: WebSocketTransport, transportName: 'web_socket' }, - { pluginsKey: 'XHRPolling', transportPlugin: XHRPolling, transportName: 'xhr_polling' }, - ]) { - describe(`with the ${scenario.pluginsKey} plugin`, () => { - /** - * Tests RTN1 support for modular bundle. - * @nospec - */ - it(`is able to use the ${scenario.transportName} transport`, async function () { - const helper = this.test.helper; - const realtime = new BaseRealtime( - helper.ablyClientOptions({ - autoConnect: false, - transports: [scenario.transportName], - plugins: { - FetchRequest, - [scenario.pluginsKey]: scenario.transportPlugin, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - let firstTransportCandidate; - const connectionManager = realtime.connection.connectionManager; - const originalTryATransport = connectionManager.tryATransport; - realtime.connection.connectionManager.tryATransport = (transportParams, candidate, callback) => { - if (!firstTransportCandidate) { - firstTransportCandidate = candidate; - } - originalTryATransport.bind(connectionManager)(transportParams, candidate, callback); - }; - - realtime.connect(); - - await realtime.connection.once('connected'); - expect(firstTransportCandidate).to.equal(scenario.transportName); - }, - realtime, - ); - }); - }); - } - }); - }); - - describe('HTTP request implementations', () => { - describe('with multiple HTTP request implementations', () => { - /** @nospec */ - it('prefers XHR', async function () { - let usedXHR = false; - - const XHRRequestSpy = class XHRRequestSpy extends XHRRequest { - static createRequest(...args) { - usedXHR = true; - return super.createRequest(...args); - } - }; - - const rest = new BaseRest( - this.test.helper.ablyClientOptions({ plugins: { FetchRequest, XHRRequest: XHRRequestSpy } }), - ); - await rest.time(); - - expect(usedXHR).to.be.true; - }); - }); - }); - - describe('MessageInteractions', () => { - describe('BaseRealtime', () => { - describe('without MessageInteractions', () => { - /** @nospec */ - it('is able to subscribe to and unsubscribe from channel events, as long as a MessageFilter isn’t passed', async function () { - const helper = this.test.helper; - const realtime = new BaseRealtime( - helper.ablyClientOptions({ plugins: { WebSocketTransport, FetchRequest } }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const channel = realtime.channels.get('channel'); - await channel.attach(); - - const subscribeReceivedMessagePromise = new Promise((resolve) => channel.subscribe(resolve)); - - await channel.publish('message', 'body'); - - const subscribeReceivedMessage = await subscribeReceivedMessagePromise; - expect(subscribeReceivedMessage.data).to.equal('body'); - }, - realtime, - ); - }); - - /** @nospec */ - it('throws an error when attempting to subscribe to channel events using a MessageFilter', async function () { - const helper = this.test.helper; - const realtime = new BaseRealtime( - helper.ablyClientOptions({ plugins: { WebSocketTransport, FetchRequest } }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const channel = realtime.channels.get('channel'); - - let thrownError = null; - try { - await channel.subscribe({ clientId: 'someClientId' }, () => {}); - } catch (error) { - thrownError = error; - } - - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('MessageInteractions plugin not provided'); - }, - realtime, - ); - }); - }); - - describe('with MessageInteractions', () => { - /** - * Tests RTL22d, MFI2e but for a MessageInteractions plugin with modular BaseRealtime. - * @nospec - */ - it('can take a MessageFilter argument when subscribing to and unsubscribing from channel events', async function () { - const helper = this.test.helper; - const realtime = new BaseRealtime( - helper.ablyClientOptions({ - plugins: { - WebSocketTransport, - FetchRequest, - MessageInteractions, - }, - }), - ); - - await monitorConnectionThenCloseAndFinish( - helper, - async () => { - const channel = realtime.channels.get('channel'); - - await channel.attach(); - - // Test `subscribe` with a filter: send two messages with different clientIds, and check that unfiltered subscription receives both messages but clientId-filtered subscription only receives the matching one. - const messageFilter = { clientId: 'someClientId' }; // note that `unsubscribe` compares filter by reference, I found that a bit surprising - - const filteredSubscriptionReceivedMessages = []; - channel.subscribe(messageFilter, (message) => { - filteredSubscriptionReceivedMessages.push(message); - }); - - const unfilteredSubscriptionReceivedFirstTwoMessagesPromise = new Promise((resolve) => { - const receivedMessages = []; - channel.subscribe(function listener(message) { - receivedMessages.push(message); - if (receivedMessages.length === 2) { - channel.unsubscribe(listener); - resolve(); - } - }); - }); - - await channel.publish(await decodeMessage({ clientId: 'someClientId' })); - await channel.publish(await decodeMessage({ clientId: 'someOtherClientId' })); - await unfilteredSubscriptionReceivedFirstTwoMessagesPromise; - - expect(filteredSubscriptionReceivedMessages.length).to.equal(1); - expect(filteredSubscriptionReceivedMessages[0].clientId).to.equal('someClientId'); - - // Test `unsubscribe` with a filter: call `unsubscribe` with the clientId filter, publish a message matching the filter, check that only the unfiltered listener recieves it - channel.unsubscribe(messageFilter); - - const unfilteredSubscriptionReceivedNextMessagePromise = new Promise((resolve) => { - channel.subscribe(function listener() { - channel.unsubscribe(listener); - resolve(); - }); - }); - - await channel.publish(await decodeMessage({ clientId: 'someClientId' })); - await unfilteredSubscriptionReceivedNextMessagePromise; - - expect(filteredSubscriptionReceivedMessages.length).to./* (still) */ equal(1); - }, - realtime, - ); - }); - }); - }); - }); - }); -} - -// This function is called by browser_setup.js once `require` is available -window.registerAblyModularTests = async () => { - return new Promise((resolve) => { - require(['shared_helper'], (Helper) => { - registerAblyModularTests(Helper); - resolve(); - }); - }); -}; diff --git a/test/browser/push.test.js b/test/browser/push.test.js deleted file mode 100644 index cef28ecc85..0000000000 --- a/test/browser/push.test.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -define(['ably', 'shared_helper', 'chai', 'push'], function (Ably, Helper, chai, PushPlugin) { - const expect = chai.expect; - const swUrl = '/push_sw.js'; - let rest; - - const persistKeys = { - deviceId: 'ably.push.deviceId', - deviceSecret: 'ably.push.deviceSecret', - deviceIdentityToken: 'ably.push.deviceIdentityToken', - pushRecipient: 'ably.push.pushRecipient', - activationState: 'ably.push.activationState', - }; - - const messageChannel = new MessageChannel(); - - /** - * These tests don't work in CI for various reasons (below) but should work when running locally via `npm run test:webserver`, provided - * that you have enabled notification permissions for the origin serving the test ui. - * - * chromium, firefox - don't support push notifications in incognito - * webkit - doesn't have a way to launch programatically with notification permissions granted - */ - if (Notification.permission === 'granted') { - describe('browser/push', function () { - this.timeout(60 * 1000); - - before(function (done) { - const helper = Helper.forHook(this); - helper.setupApp(function () { - rest = helper.AblyRest({ - pushServiceWorkerUrl: swUrl, - plugins: { Push: PushPlugin }, - }); - done(); - }); - }); - - beforeEach(async function () { - Object.values(persistKeys).forEach((key) => { - Ably.Realtime.Platform.Config.push.storage.remove(key); - }); - - const worker = await navigator.serviceWorker.getRegistration(swUrl); - - if (worker) { - await worker.unregister(); - } - }); - - afterEach(async function () { - await rest.push.deactivate(); - }); - - /** @spec RSH2a */ - it('push_activation_succeeds', async function () { - await rest.push.activate(); - expect(rest.device.deviceIdentityToken).to.be.ok; - }); - - /** @nospec */ - it('direct_publish_device_id', async function () { - await rest.push.activate(); - - const pushRecipient = { - deviceId: rest.device.id, - }; - - const pushPayload = { - notification: { title: 'Test message', body: 'Test message body' }, - data: { foo: 'bar' }, - }; - - const sw = await navigator.serviceWorker.getRegistration(swUrl); - - sw.active.postMessage({ type: 'INIT_PORT' }, [messageChannel.port2]); - - const receivedPushPayload = await new Promise((resolve, reject) => { - messageChannel.port1.onmessage = function (event) { - resolve(event.data.payload); - }; - - rest.push.admin.publish(pushRecipient, pushPayload).catch(reject); - }); - - expect(receivedPushPayload.data).to.deep.equal(pushPayload.data); - expect(receivedPushPayload.notification.title).to.equal(pushPayload.notification.title); - expect(receivedPushPayload.notification.body).to.equal(pushPayload.notification.body); - }); - }); - } -}); diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js deleted file mode 100644 index 5e4e15df80..0000000000 --- a/test/browser/simple.test.js +++ /dev/null @@ -1,261 +0,0 @@ -'use strict'; - -define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { - var expect = chai.expect; - - describe('browser/simple', function () { - this.timeout(60 * 1000); - before(function (done) { - const helper = Helper.forHook(this); - helper.setupApp(function (err) { - if (err) { - done(err); - return; - } - done(); - }); - }); - - function isTransportAvailable(transport) { - return transport in Ably.Realtime.ConnectionManager.supportedTransports(Ably.Realtime._transports); - } - - function realtimeConnection(helper, transports) { - var options = {}; - if (transports) options.transports = transports; - return helper.AblyRealtime(options); - } - - function failWithin(timeInSeconds, done, ably, description) { - var timeout = setTimeout(function () { - done(new Error('Timed out: Trying to ' + description + ' took longer than ' + timeInSeconds + ' second(s)')); - ably.close(); - }, timeInSeconds * 1000); - - return { - stop: function () { - clearTimeout(timeout); - }, - }; - } - - function connectionWithTransport(done, helper, transport) { - try { - var ably = realtimeConnection(helper, transport && [transport]), - connectionTimeout = failWithin(10, done, ably, 'connect'); - ably.connection.on('connected', function () { - connectionTimeout.stop(); - done(); - ably.close(); - }); - var exitOnState = function (state) { - ably.connection.on(state, function () { - connectionTimeout.stop(); - done(new Error(transport + ' connection to server failed')); - ably.close(); - }); - }; - exitOnState('failed'); - exitOnState('suspended'); - } catch (err) { - done(err); - connectionTimeout.stop(); - } - } - - function heartbeatWithTransport(done, helper, transport) { - try { - var ably = realtimeConnection(helper, transport && [transport]), - connectionTimeout = failWithin(10, done, ably, 'connect'), - heartbeatTimeout; - - ably.connection.on('connected', function () { - connectionTimeout.stop(); - heartbeatTimeout = failWithin(25, done, ably, 'wait for heartbeat'); - Helper.whenPromiseSettles(ably.connection.ping(), function (err) { - heartbeatTimeout.stop(); - done(err); - ably.close(); - }); - }); - var exitOnState = function (state) { - ably.connection.on(state, function () { - connectionTimeout.stop(); - done(new Error(transport + ' connection to server failed')); - ably.close(); - }); - }; - exitOnState('failed'); - exitOnState('suspended'); - } catch (err) { - done(err); - connectionTimeout.stop(); - if (heartbeatTimeout) heartbeatTimeout.stop(); - } - } - - function publishWithTransport(done, helper, transport) { - var count = 5; - var sentCount = 0, - receivedCount = 0, - sentCbCount = 0; - var timer; - var checkFinish = function () { - if (receivedCount === count && sentCbCount === count) { - receiveMessagesTimeout.stop(); - done(); - ably.close(); - } - }; - var ably = realtimeConnection(helper, transport && [transport]), - connectionTimeout = failWithin(5, done, ably, 'connect'), - receiveMessagesTimeout; - - ably.connection.on('connected', function () { - connectionTimeout.stop(); - receiveMessagesTimeout = failWithin(15, done, ably, 'wait for published messages to be received'); - - timer = setInterval(function () { - Helper.whenPromiseSettles(channel.publish('event0', 'Hello world at: ' + new Date()), function (err) { - sentCbCount++; - checkFinish(); - }); - if (sentCount === count) clearInterval(timer); - }, 500); - }); - - var channel = ably.channels.get(transport + 'publish0' + String(Math.random()).substr(1)); - /* subscribe to event */ - channel.subscribe('event0', function () { - receivedCount++; - checkFinish(); - }); - } - - /** - * Related to RTN4, but this test just reconfirms this behaviour in a browser. - * - * @nospec - */ - it('simpleInitBase0', function (done) { - try { - var helper = this.test.helper, - timeout, - ably = realtimeConnection(helper); - - ably.connection.on('connected', function () { - clearTimeout(timeout); - done(); - ably.close(); - }); - var exitOnState = function (state) { - ably.connection.on(state, function () { - done(new Error('Connection to server failed')); - ably.close(); - }); - }; - exitOnState('failed'); - exitOnState('suspended'); - timeout = setTimeout(function () { - done(new Error('Timed out: Trying to connect took longer than expected')); - ably.close(); - }, 10 * 1000); - } catch (err) { - done(err); - } - }); - - var wsTransport = 'web_socket'; - if (isTransportAvailable(wsTransport)) { - /** - * Related to RTN1 and RTN4. does basic connection test for websocket transport in a browser. - * - * @nospec - */ - it('wsbase0', function (done) { - connectionWithTransport(done, this.test.helper, wsTransport); - }); - - /** - * Publish and subscribe, json transport - * Tests RTL6i1 and RTL7b for websocket transport in a browser - * - * @nospec - */ - it('wspublish0', function (done) { - publishWithTransport(done, this.test.helper, wsTransport); - }); - - /** - * Check heartbeat - * Tests RTN13a, RTN13b and RTN13c for websocket transport in a browser - * - * @nospec - */ - it('wsheartbeat0', function (done) { - heartbeatWithTransport(done, this.test.helper, wsTransport); - }); - } - - var xhrPollingTransport = 'xhr_polling'; - if (isTransportAvailable(xhrPollingTransport)) { - /** - * Related to RTN1 and RTN4. does basic connection test for xhr polling transport in a browser - * - * @nospec - */ - it('xhrpollingbase0', function (done) { - connectionWithTransport(done, this.test.helper, xhrPollingTransport); - }); - - /** - * Publish and subscribe, json transport - * Tests RTL6i1 and RTL7b for xhr polling transport in a browser - * - * @nospec - */ - it('xhrpollingpublish0', function (done) { - publishWithTransport(done, this.test.helper, xhrPollingTransport); - }); - - /** - * Check heartbeat - * Tests RTN13a, RTN13b and RTN13c for xhr polling transport in a browser - * - * @nospec - */ - it('xhrpollingheartbeat0', function (done) { - heartbeatWithTransport(done, this.test.helper, xhrPollingTransport); - }); - } - - /** - * Related to RTN1 and RTN4: does basic connection test in a browser with automatically choosing a transport - * - * @nospec - */ - it('auto_transport_base0', function (done) { - connectionWithTransport(done, this.test.helper); - }); - - /** - * Publish and subscribe - * Tests RTL6i1 and RTL7b in a browser with automatically choosing a transport - * - * @nospec - */ - it('auto_transport_publish0', function (done) { - publishWithTransport(done, this.test.helper); - }); - - /** - * Check heartbeat - * Tests RTN13a, RTN13b and RTN13c in a browser with automatically choosing a transport - * - * @nospec - */ - it('auto_transport_heartbeat0', function (done) { - heartbeatWithTransport(done, this.test.helper); - }); - }); -}); diff --git a/test/mocha.html b/test/mocha.html index 490230b487..376e39478e 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -22,7 +22,6 @@ - diff --git a/test/playwright.html b/test/playwright.html index 22a0bf0ca1..c91d41a5bd 100644 --- a/test/playwright.html +++ b/test/playwright.html @@ -21,7 +21,6 @@ - diff --git a/test/realtime/transports.test.js b/test/realtime/transports.test.js deleted file mode 100644 index e947d89194..0000000000 --- a/test/realtime/transports.test.js +++ /dev/null @@ -1,259 +0,0 @@ -'use strict'; - -define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai, Ably) { - const expect = chai.expect; - const Defaults = Ably.Rest.Platform.Defaults; - const originialWsCheckUrl = Defaults.wsConnectivityUrl; - const transportPreferenceName = 'ably-transport-preference'; - const localStorageSupported = globalThis.localStorage; - const urlScheme = 'https://'; - const echoServer = 'echo.ably.io'; - const failUrl = urlScheme + echoServer + '/respondwith?status=500'; - const defaultTransports = new Ably.Realtime({ key: 'xxx:yyy', autoConnect: false }).connection.connectionManager - .transports; - - function baseTransport(helper) { - return new Ably.Realtime({ - key: 'xxx:yyy', - autoConnect: false, - transports: helper.availableTransports, - }).connection.connectionManager.baseTransport; - } - - function restoreWsConnectivityUrl() { - Defaults.wsConnectivityUrl = originialWsCheckUrl; - } - - const Config = Ably.Rest.Platform.Config; - const oldWs = Config.WebSocket; - - function restoreWebSocketConstructor() { - Config.WebSocket = oldWs; - } - - // drop in replacement for WebSocket which doesn't emit any events (same behaviour as when WebSockets upgrade headers are removed by a proxy) - class FakeWebSocket { - close() {} - } - - describe('realtime/transports', function () { - this.timeout(60 * 1000); - - before(function (done) { - const helper = Helper.forHook(this); - helper.setupApp(function (err) { - if (err) { - done(err); - return; - } - done(); - }); - }); - - afterEach(restoreWsConnectivityUrl); - afterEach(restoreWebSocketConstructor); - - if ( - Helper.forTestDefinition(this, 'tests that are run if there are multiple transports').availableTransports.length > - 1 - ) { - // ensure comet transport is used for nodejs tests - function options(helper, opts) { - return helper.Utils.mixin( - { - transports: helper.availableTransports, - }, - opts || {}, - ); - } - - /** @nospec */ - it('websocket_is_default', function (done) { - const helper = this.test.helper; - const realtime = helper.AblyRealtime(options(helper)); - - realtime.connection.on('connected', function () { - try { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - } - helper.closeAndFinish(done, realtime); - }); - - helper.monitorConnection(done, realtime); - }); - - /** @nospec */ - it('no_ws_connectivity', function (done) { - const helper = this.test.helper; - Config.WebSocket = FakeWebSocket; - const realtime = helper.AblyRealtime( - options(helper, { webSocketSlowTimeout: 1000, webSocketConnectTimeout: 3000 }), - ); - - realtime.connection.on('connected', function () { - try { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal( - baseTransport(helper), - ); - // check that transport preference is set - if (localStorageSupported) { - expect(window.localStorage.getItem(transportPreferenceName)).to.equal( - JSON.stringify({ value: baseTransport(helper) }), - ); - } - } catch (err) { - helper.closeAndFinish(done, realtime, err); - } - helper.closeAndFinish(done, realtime); - }); - - helper.monitorConnection(done, realtime); - }); - - /** @nospec */ - it('ws_primary_host_fails', function (done) { - const helper = this.test.helper; - const goodHost = helper.AblyRest().options.realtimeHost; - const realtime = helper.AblyRealtime( - options(helper, { realtimeHost: helper.unroutableAddress, fallbackHosts: [goodHost] }), - ); - - realtime.connection.on('connected', function () { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); - helper.closeAndFinish(done, realtime); - }); - - helper.monitorConnection(done, realtime); - }); - - /** @specpartial RTN14d */ - it('no_internet_connectivity', function (done) { - const helper = this.test.helper; - Config.WebSocket = FakeWebSocket; - const realtime = helper.AblyRealtime( - options(helper, { connectivityCheckUrl: failUrl, webSocketSlowTimeout: 1000 }), - ); - - // expect client to transition to disconnected rather than attempting base transport (which would succeed in this instance) - realtime.connection.on('disconnected', function () { - helper.closeAndFinish(done, realtime); - }); - }); - - /** @specpartial RTN14d */ - it('no_websocket_or_base_transport', function (done) { - const helper = this.test.helper; - Config.WebSocket = FakeWebSocket; - const realtime = helper.AblyRealtime({ - transports: ['web_socket'], - realtimeRequestTimeout: 3000, - webSocketConnectTimeout: 3000, - }); - - realtime.connection.on('disconnected', function () { - helper.closeAndFinish(done, realtime); - }); - }); - - if (localStorageSupported) { - /** @nospec */ - it('base_transport_preference', function (done) { - const helper = this.test.helper; - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport(helper) })); - const realtime = helper.AblyRealtime(options(helper)); - - // make ws connectivity check only resolve after connected with base transport. - // prevents a race condition where the wsConnectivity check succeeds before base transport is activated; - // in this case the base transport would be abandoned in favour of websocket - realtime.connection.connectionManager.checkWsConnectivity = function () { - return new Promise((resolve) => { - realtime.connection.once('connected', () => { - resolve(); - }); - }); - }; - - realtime.connection.on('connected', function () { - try { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal( - baseTransport(helper), - ); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - } - helper.closeAndFinish(done, realtime); - }); - helper.monitorConnection(done, realtime); - }); - - /** @nospec */ - it('transport_preference_reset_while_connecting', function (done) { - const helper = this.test.helper; - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport(helper) })); - const realtime = helper.AblyRealtime(options(helper)); - - // make ws connectivity check fast so that it succeeds while base transport is still connecting - realtime.connection.connectionManager.checkWsConnectivity = function () { - return new Promise((resolve) => { - setTimeout(() => resolve(), 1); - }); - }; - - realtime.connection.once('connected', function () { - try { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); - expect(realtime.connection.connectionManager.getTransportPreference()).to.equal('web_socket'); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - helper.closeAndFinish(done, realtime); - }); - helper.monitorConnection(done, realtime); - }); - - /** @nospec */ - it('transport_preference_reset_after_connected', function (done) { - const helper = this.test.helper; - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport(helper) })); - const realtime = helper.AblyRealtime(options(helper)); - - // make ws connectivity check only resolve after connected with base transport - realtime.connection.connectionManager.checkWsConnectivity = function () { - return new Promise((resolve) => { - realtime.connection.once('connected', () => { - try { - expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal( - baseTransport(helper), - ); - resolve(); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - }); - }); - }; - - realtime.connection.once('connected', function () { - // the checkWsConnectivity promise won't execute .then callbacks synchronously upon resolution - // so we need to wait one tick before the transport preference is unpersisted - setTimeout(() => { - try { - // ensure base transport preference is erased - expect(realtime.connection.connectionManager.getTransportPreference()).to.equal(null); - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - helper.closeAndFinish(done, realtime); - }, 0); - }); - helper.monitorConnection(done, realtime); - }); - } - } - }); -}); diff --git a/test/rest/bufferutils.test.js b/test/rest/bufferutils.test.js deleted file mode 100644 index e4c0047169..0000000000 --- a/test/rest/bufferutils.test.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -define(['ably', 'chai'], function (Ably, chai) { - var expect = chai.expect; - var BufferUtils = Ably.Realtime.Platform.BufferUtils; - var testString = 'test'; - var testBase64 = 'dGVzdA=='; - var testHex = '74657374'; - - describe('rest/bufferutils', function () { - /** @nospec */ - it('Basic encoding and decoding', function () { - /* base64 */ - expect(BufferUtils.base64Encode(BufferUtils.utf8Encode(testString))).to.equal(testBase64); - expect(BufferUtils.utf8Decode(BufferUtils.base64Decode(testBase64))).to.equal(testString); - - /* hex */ - expect(BufferUtils.hexEncode(BufferUtils.utf8Encode(testString))).to.equal(testHex); - expect(BufferUtils.utf8Decode(BufferUtils.hexDecode(testHex))).to.equal(testString); - - /* compare */ - expect( - BufferUtils.areBuffersEqual(BufferUtils.utf8Encode(testString), BufferUtils.utf8Encode(testString)), - ).to.equal(true); - expect( - BufferUtils.areBuffersEqual(BufferUtils.utf8Encode(testString), BufferUtils.utf8Encode('other')), - ).to.not.equal(true); - }); - - /** - * In node it's idiomatic for most methods dealing with binary data to - * return Buffers. In the browser it's more idiomatic to return ArrayBuffers. - * - * @nospec - */ - it('BufferUtils return correct types', function () { - if (typeof Buffer !== 'undefined') { - /* node */ - expect(BufferUtils.utf8Encode(testString).constructor).to.equal(Buffer); - expect(BufferUtils.hexDecode(testHex).constructor).to.equal(Buffer); - expect(BufferUtils.base64Decode(testBase64).constructor).to.equal(Buffer); - expect(BufferUtils.toBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(Buffer); - expect(BufferUtils.toArrayBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(ArrayBuffer); - } else { - /* modern browsers */ - expect(BufferUtils.utf8Encode(testString).constructor).to.equal(ArrayBuffer); - expect(BufferUtils.hexDecode(testHex).constructor).to.equal(ArrayBuffer); - expect(BufferUtils.base64Decode(testBase64).constructor).to.equal(ArrayBuffer); - expect(BufferUtils.toBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(Uint8Array); - expect(BufferUtils.toArrayBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(ArrayBuffer); - } - }); - }); -}); diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 80d5d8d8b1..4862ded73c 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -44,11 +44,9 @@ window.__testFiles__.files = { 'test/realtime/reauth.test.js': true, 'test/realtime/resume.test.js': true, 'test/realtime/sync.test.js': true, - 'test/realtime/transports.test.js': true, 'test/realtime/utils.test.js': true, 'test/rest/api.test.js': true, 'test/rest/auth.test.js': true, - 'test/rest/bufferutils.test.js': true, 'test/rest/capability.test.js': true, 'test/rest/defaults.test.js': true, 'test/rest/fallbacks.test.js': true, @@ -61,10 +59,6 @@ window.__testFiles__.files = { 'test/rest/request.test.js': true, 'test/rest/stats.test.js': true, 'test/rest/time.test.js': true, - 'test/browser/connection.test.js': true, - 'test/browser/simple.test.js': true, - 'test/browser/http.test.js': true, - 'test/browser/push.test.js': true, 'test/rest/status.test.js': true, 'test/rest/batch.test.js': true, }; diff --git a/test/support/browser_setup.js b/test/support/browser_setup.js index 2cab40ad30..8ad4f9d657 100644 --- a/test/support/browser_setup.js +++ b/test/support/browser_setup.js @@ -78,9 +78,6 @@ require([(baseUrl + '/test/common/globals/named_dependencies.js').replace('//', callback: () => { // (For some reason things don’t work if you return a Promise from this callback, hence the nested async function) (async () => { - // Let modular.test.js register its tests before we run the test suite - await registerAblyModularTests(); - // we have to kickoff mocha mocha.run(); })(); From e94a8ae81848fad0c227fb28d09111ba51d11b10 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 20 Jun 2024 14:32:17 -0300 Subject: [PATCH 2/5] =?UTF-8?q?[UTS]=20Don=E2=80=99t=20test=20on=20multipl?= =?UTF-8?q?e=20transports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is part of removing some common private API usage for the unified test suite. ably-js is the only client library that offers multiple transports, so testing on multiple transports will probably not be part of the UTS. --- test/common/modules/shared_helper.js | 35 +---- test/realtime/failure.test.js | 216 ++++++++++++++------------- test/realtime/init.test.js | 66 ++++---- test/realtime/presence.test.js | 2 +- 4 files changed, 145 insertions(+), 174 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index e13ec6bcdb..b072382397 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -112,21 +112,6 @@ define([ return new this(this.createContext(context)); } - static forTestDefinition(thisInDescribe, label) { - if (!label) { - throw new Error('SharedHelper.forTestDefinition called without label'); - } - - const context = { - type: 'definition', - file: thisInDescribe.file, - suite: this.extractSuiteHierarchy(thisInDescribe), - label, - }; - - return new this(this.createContext(context)); - } - recordTestStart() { this.privateApiContext.recordTestStart(); } @@ -331,7 +316,6 @@ define([ /* testFn is assumed to be a function of realtimeOptions that returns a mocha test */ static testOnAllTransports(thisInDescribe, name, testFn, skip) { - const helper = this.forTestDefinition(thisInDescribe, name).addingHelperFunction('testOnAllTransports'); var itFn = skip ? it.skip : it; function createTest(options) { @@ -341,23 +325,8 @@ define([ }; } - let transports = helper.availableTransports; - transports.forEach(function (transport) { - itFn( - name + '_with_' + transport + '_binary_transport', - createTest({ transports: [transport], useBinaryProtocol: true }), - ); - itFn( - name + '_with_' + transport + '_text_transport', - createTest({ transports: [transport], useBinaryProtocol: false }), - ); - }); - /* Plus one for no transport specified (ie use websocket/base mechanism if - * present). (we explicitly specify all transports since node only does - * websocket+nodecomet if comet is explicitly requested) - * */ - itFn(name + '_with_binary_transport', createTest({ transports, useBinaryProtocol: true })); - itFn(name + '_with_text_transport', createTest({ transports, useBinaryProtocol: false })); + itFn(name + '_with_binary_transport', createTest({ useBinaryProtocol: true })); + itFn(name + '_with_text_transport', createTest({ useBinaryProtocol: false })); } static restTestOnJsonMsgpack(name, testFn, skip) { diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 970e7b90e4..e539a7e638 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -201,51 +201,48 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async expect(value).to.be.below(max); } - Helper.forTestDefinition(this, 'disconnected_backoff_').availableTransports.forEach(function (transport) { - /** - * @spec RTB1a - * @spec RTB1b - * @spec TO3l1 - * @specpartial RTB1 - test backoff for connection - * @specpartial RTN14d - retries and retryIn calculation - * @specpartial TA2 - retryIn passed in ConnectionStateChange - */ - it('disconnected_backoff_' + transport, function (done) { - const helper = this.test.helper; - var disconnectedRetryTimeout = 150; - var realtime = helper.AblyRealtime({ - disconnectedRetryTimeout: disconnectedRetryTimeout, - realtimeHost: 'invalid', - restHost: 'invalid', - transports: [transport], - }); + /** + * @spec RTB1a + * @spec RTB1b + * @spec TO3l1 + * @specpartial RTB1 - test backoff for connection + * @specpartial RTN14d - retries and retryIn calculation + * @specpartial TA2 - retryIn passed in ConnectionStateChange + */ + it('disconnected_backoff', function (done) { + const helper = this.test.helper; + var disconnectedRetryTimeout = 150; + var realtime = helper.AblyRealtime({ + disconnectedRetryTimeout: disconnectedRetryTimeout, + realtimeHost: 'invalid', + restHost: 'invalid', + }); - var retryCount = 0; - var retryTimeouts = []; + var retryCount = 0; + var retryTimeouts = []; - realtime.connection.on(function (stateChange) { - if (stateChange.previous === 'connecting' && stateChange.current === 'disconnected') { - if (retryCount > 4) { - // Upper bound = min((retryAttempt + 2) / 3, 2) * initialTimeout - // Lower bound = 0.8 * Upper bound - checkIsBetween(retryTimeouts[0], 120, 150); - checkIsBetween(retryTimeouts[1], 160, 200); - checkIsBetween(retryTimeouts[2], 200, 250); + realtime.connection.on(function (stateChange) { + if (stateChange.previous === 'connecting' && stateChange.current === 'disconnected') { + if (retryCount > 4) { + // Upper bound = min((retryAttempt + 2) / 3, 2) * initialTimeout + // Lower bound = 0.8 * Upper bound + checkIsBetween(retryTimeouts[0], 120, 150); + checkIsBetween(retryTimeouts[1], 160, 200); + checkIsBetween(retryTimeouts[2], 200, 250); - for (var i = 3; i < retryTimeouts.length; i++) { - checkIsBetween(retryTimeouts[i], 240, 300); - } - helper.closeAndFinish(done, realtime); - return; - } - try { - retryTimeouts.push(stateChange.retryIn); - retryCount += 1; - } catch (err) { - helper.closeAndFinish(done, realtime, err); + for (var i = 3; i < retryTimeouts.length; i++) { + checkIsBetween(retryTimeouts[i], 240, 300); } + helper.closeAndFinish(done, realtime); + return; } - }); + try { + retryTimeouts.push(stateChange.retryIn); + retryCount += 1; + } catch (err) { + helper.closeAndFinish(done, realtime, err); + } + } }); }); @@ -405,75 +402,84 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async }); }); - Helper.forTestDefinition(this, 'channel_backoff_').availableTransports.forEach(function (transport) { - /** - * @spec RTL13b - * @spec RTB1a - * @spec RTB1b - * @spec TO3l7 - * @specpartial RTB1 - test backoff for channel - */ - it('channel_backoff_' + transport, function (done) { - const helper = this.test.helper; - var channelRetryTimeout = 150; - var realtime = helper.AblyRealtime({ - channelRetryTimeout: channelRetryTimeout, - transports: [transport], - }), - channel = realtime.channels.get('failed_attach'), - originalProcessMessage = channel.processMessage.bind(channel), - retryCount = 0; - - var performance = isBrowser ? window.performance : require('perf_hooks').performance; - - helper.recordPrivateApi('replace.channel.processMessage'); - channel.processMessage = async function (message) { - // Ignore ATTACHED messages - if (message.action === 11) { - return; - } - helper.recordPrivateApi('call.channel.processMessage'); - await originalProcessMessage(message); - }; + /** + * @spec RTL13b + * @spec RTB1a + * @spec RTB1b + * @spec TO3l7 + * @specpartial RTB1 - test backoff for channel + */ + it('channel_backoff', function (done) { + const helper = this.test.helper; + var channelRetryTimeout = 150; + var realtime = helper.AblyRealtime({ + channelRetryTimeout: channelRetryTimeout, + }), + channel = realtime.channels.get('failed_attach'), + originalProcessMessage = channel.processMessage.bind(channel), + retryCount = 0; - var retryTimeouts = []; + var performance = isBrowser ? window.performance : require('perf_hooks').performance; - realtime.connection.on('connected', function () { - helper.recordPrivateApi('write.realtime.options.timeouts.realtimeRequestTimeout'); - realtime.options.timeouts.realtimeRequestTimeout = 1; - Helper.whenPromiseSettles(channel.attach(), function (err) { - if (err) { - var lastSuspended = performance.now(); - channel.on(function (stateChange) { - if (stateChange.current === 'suspended') { - if (retryCount > 4) { - // Upper bound = min((retryAttempt + 2) / 3, 2) * initialTimeout - // Lower bound = 0.8 * Upper bound - // Additional 10 is a calculationDelayTimeout - checkIsBetween(retryTimeouts[0], 120, 150 + 10); - checkIsBetween(retryTimeouts[1], 160, 200 + 10); - checkIsBetween(retryTimeouts[2], 200, 250 + 10); - - for (var i = 3; i < retryTimeouts.length; i++) { - checkIsBetween(retryTimeouts[i], 240, 300 + 10); - } - helper.closeAndFinish(done, realtime); - return; - } - var elapsedSinceSuspneded = performance.now() - lastSuspended; - lastSuspended = performance.now(); - try { - retryTimeouts.push(elapsedSinceSuspneded); - retryCount += 1; - } catch (err) { - helper.closeAndFinish(done, realtime, err); + helper.recordPrivateApi('replace.channel.processMessage'); + channel.processMessage = async function (message) { + // Ignore ATTACHED messages + if (message.action === 11) { + return; + } + helper.recordPrivateApi('call.channel.processMessage'); + await originalProcessMessage(message); + }; + + var performance = isBrowser ? window.performance : require('perf_hooks').performance; + + helper.recordPrivateApi('replace.channel.processMessage'); + channel.processMessage = async function (message) { + // Ignore ATTACHED messages + if (message.action === 11) { + return; + } + helper.recordPrivateApi('call.channel.processMessage'); + await originalProcessMessage(message); + }; + + var retryTimeouts = []; + + realtime.connection.on('connected', function () { + helper.recordPrivateApi('write.realtime.options.timeouts.realtimeRequestTimeout'); + realtime.options.timeouts.realtimeRequestTimeout = 1; + Helper.whenPromiseSettles(channel.attach(), function (err) { + if (err) { + var lastSuspended = performance.now(); + channel.on(function (stateChange) { + if (stateChange.current === 'suspended') { + if (retryCount > 4) { + // Upper bound = min((retryAttempt + 2) / 3, 2) * initialTimeout + // Lower bound = 0.8 * Upper bound + // Additional 10 is a calculationDelayTimeout + checkIsBetween(retryTimeouts[0], 120, 150 + 10); + checkIsBetween(retryTimeouts[1], 160, 200 + 10); + checkIsBetween(retryTimeouts[2], 200, 250 + 10); + + for (var i = 3; i < retryTimeouts.length; i++) { + checkIsBetween(retryTimeouts[i], 240, 300 + 10); } + helper.closeAndFinish(done, realtime); + return; } - }); - } else { - helper.closeAndFinish(done, realtime, new Error('Expected channel attach to timeout')); - } - }); + var elapsedSinceSuspneded = performance.now() - lastSuspended; + lastSuspended = performance.now(); + try { + retryTimeouts.push(elapsedSinceSuspneded); + retryCount += 1; + } catch (err) { + helper.closeAndFinish(done, realtime, err); + } + } + }); + } else { + helper.closeAndFinish(done, realtime, new Error('Expected channel attach to timeout')); + } }); }); }); diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index af15253c0a..02dbb732b6 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -16,47 +16,43 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { }); }); - /* Restrict to websocket or xhr polling for the v= test as if stream=false the - * recvRequest may not be the connectRequest by the time we check it. */ - ((testDefinitionHelper) => { - if (testDefinitionHelper.bestTransport === 'web_socket' || testDefinitionHelper.bestTransport === 'xhr_polling') { - /* - * Base init case. - * @spec RTN2f - */ - it('initbase0', function (done) { - const helper = this.test.helper; - var realtime; + /* + * Base init case. + * @spec RTN2f + */ + it('initbase0', function (done) { + const helper = this.test.helper; + var realtime; + try { + /* Restrict to websocket or xhr polling for the v= test as if stream=false the + * recvRequest may not be the connectRequest by the time we check it. */ + realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_polling'] }); + realtime.connection.on('connected', function () { + /* check api version */ + helper.recordPrivateApi('read.connectionManager.activeProtocol.transport'); + var transport = realtime.connection.connectionManager.activeProtocol.transport; + var connectUri = helper.isWebsocket(transport) + ? (() => { + helper.recordPrivateApi('read.transport.uri'); + return transport.uri; + })() + : (() => { + helper.recordPrivateApi('read.transport.recvRequest.recvUri'); + return transport.recvRequest.recvUri; + })(); try { - realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_polling'] }); - realtime.connection.on('connected', function () { - /* check api version */ - helper.recordPrivateApi('read.connectionManager.activeProtocol.transport'); - var transport = realtime.connection.connectionManager.activeProtocol.transport; - var connectUri = helper.isWebsocket(transport) - ? (() => { - helper.recordPrivateApi('read.transport.uri'); - return transport.uri; - })() - : (() => { - helper.recordPrivateApi('read.transport.recvRequest.recvUri'); - return transport.recvRequest.recvUri; - })(); - try { - expect(connectUri.indexOf('v=3') > -1, 'Check uri includes v=3').to.be.ok; - } catch (err) { - helper.closeAndFinish(done, realtime, err); - return; - } - helper.closeAndFinish(done, realtime); - }); - helper.monitorConnection(done, realtime); + expect(connectUri.indexOf('v=3') > -1, 'Check uri includes v=3').to.be.ok; } catch (err) { helper.closeAndFinish(done, realtime, err); + return; } + helper.closeAndFinish(done, realtime); }); + helper.monitorConnection(done, realtime); + } catch (err) { + helper.closeAndFinish(done, realtime, err); } - })(Helper.forTestDefinition(this, 'initbase0')); + }); /** * Init with key string. diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index f2de186fdc..1f4647c0f8 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1913,7 +1913,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async */ it('leave_published_for_member_missing_from_sync', function (done) { var helper = this.test.helper, - realtime = helper.AblyRealtime({ transports: helper.availableTransports }), + realtime = helper.AblyRealtime(), continuousClientId = 'continuous', goneClientId = 'gone', continuousRealtime = helper.AblyRealtime({ clientId: continuousClientId }), From e4628711262747ce129aec186886ca1c0fbbe809 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 20 Jun 2024 15:20:59 -0300 Subject: [PATCH 3/5] [UTS] Disable use of bestTransport / availableTransport private APIs This is part of removing some common private API usage for the unified test suite. As mentioned in e94a8ae, client libraries running in unified test suite are expected to only use WebSocket transport. --- test/common/modules/shared_helper.js | 17 +---------------- test/realtime/init.test.js | 3 ++- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index b072382397..5deb3199f1 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -121,25 +121,10 @@ define([ } get availableTransports() { - const helper = this.addingHelperFunction('availableTransports'); - return helper._availableTransports; - } - - get _availableTransports() { - this.recordPrivateApi('call.Utils.keysArray'); - this.recordPrivateApi('call.ConnectionManager.supportedTransports'); - this.recordPrivateApi('read.Realtime._transports'); - return utils.keysArray( - clientModule.Ably.Realtime.ConnectionManager.supportedTransports(clientModule.Ably.Realtime._transports), - ); + return ['web_socket']; } get bestTransport() { - const helper = this.addingHelperFunction('bestTransport'); - return helper._bestTransport; - } - - get _bestTransport() { return this.availableTransports[0]; } diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 02dbb732b6..79e9844d3a 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -403,7 +403,8 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { try { realtime = helper.AblyRealtime({ transports: helper.availableTransports }); helper.recordPrivateApi('read.connectionManager.baseTransport'); - expect(realtime.connection.connectionManager.baseTransport).to.equal('comet'); + // There’s no base transport now that we’re only specifiying web_socket + //expect(realtime.connection.connectionManager.baseTransport).to.equal('comet'); helper.recordPrivateApi('read.connectionManager.webSocketTransportAvailable'); expect(realtime.connection.connectionManager.webSocketTransportAvailable).to.be.ok; helper.closeAndFinish(done, realtime); From 1fb77af7d7a0eaccc603837d8e10dd016131dd50 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Jun 2024 09:31:10 -0300 Subject: [PATCH 4/5] =?UTF-8?q?[UTS]=20Don=E2=80=99t=20use=20private=20API?= =?UTF-8?q?s=20in=20closeAndFinish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is part of removing some common private API usage for the unified test suite. It’s not clear to me why these private APIs were required. Even `nextTick`, which had a comment saying that it is needed by “channelattach_publish_invalid etc” — removing the `nextTick` does not seem to affect those tests (although 4a90e75, which introduced the `nextTick` usage, does describe it as a “race” so perhaps we need to see if failures emerge over time). --- test/common/modules/shared_helper.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 5deb3199f1..420ef5714c 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -248,27 +248,16 @@ define([ } _callbackOnClose(realtime, callback) { - this.recordPrivateApi('read.connectionManager.activeProtocol'); - if (!realtime.connection.connectionManager.activeProtocol) { - this.recordPrivateApi('call.Platform.nextTick'); - platform.Config.nextTick(function () { - realtime.close(); - callback(); - }); + if (realtime.connection.state === 'closed' || realtime.connection.state === 'failed') { + callback(); return; } - this.recordPrivateApi('read.connectionManager.activeProtocol.transport'); - this.recordPrivateApi('listen.transport.disposed'); - realtime.connection.connectionManager.activeProtocol.transport.on('disposed', function () { + + realtime.connection.once(['closed', 'failed'], function () { callback(); }); - /* wait a tick before closing in order to avoid the final close - * happening synchronously in a publish/attach callback, which - * complicates channelattach_publish_invalid etc. */ - this.recordPrivateApi('call.Platform.nextTick'); - platform.Config.nextTick(function () { - realtime.close(); - }); + + realtime.close(); } closeAndFinishSeveral(done, realtimeArray, e) { From 2d91b4140c32b1e027b25a8f9be5b0ce39b198d0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 Jul 2024 11:01:17 -0300 Subject: [PATCH 5/5] [UTS] Add some exclusions to private API reporting These are APIs that for the purposes of the unified test suite we can not consider as private for the time being (so as to not clutter up the private API reporting data). --- scripts/processPrivateApiData/exclusions.ts | 22 +++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/processPrivateApiData/exclusions.ts b/scripts/processPrivateApiData/exclusions.ts index 7578063c44..b393835254 100644 --- a/scripts/processPrivateApiData/exclusions.ts +++ b/scripts/processPrivateApiData/exclusions.ts @@ -6,11 +6,25 @@ type ExclusionRule = { helper?: string; }; -/** - * This exclusions mechanism is not currently being used on `main`, but I will use it on a separate unified test suite branch in order to exclude some private API usage that can currently be disregarded in the context of the unified test suite. - */ export function applyingExclusions(usageDtos: PrivateApiUsageDto[]) { - const exclusionRules: ExclusionRule[] = []; + const exclusionRules: ExclusionRule[] = [ + // This is all helper stuff that we could pull into the test suite, and which for now we could just continue using the version privately exposed by ably-js, even in the UTS. + { privateAPIIdentifier: 'call.BufferUtils.areBuffersEqual' }, + { privateAPIIdentifier: 'call.BufferUtils.base64Decode' }, + { privateAPIIdentifier: 'call.BufferUtils.base64Encode' }, + { privateAPIIdentifier: 'call.BufferUtils.hexEncode' }, + { privateAPIIdentifier: 'call.BufferUtils.isBuffer' }, + { privateAPIIdentifier: 'call.BufferUtils.toArrayBuffer' }, + { privateAPIIdentifier: 'call.BufferUtils.utf8Encode' }, + { privateAPIIdentifier: 'call.Utils.copy' }, + { privateAPIIdentifier: 'call.Utils.inspectError' }, + { privateAPIIdentifier: 'call.Utils.keysArray' }, + { privateAPIIdentifier: 'call.Utils.mixin' }, + { privateAPIIdentifier: 'call.Utils.toQueryString' }, + { privateAPIIdentifier: 'call.msgpack.decode' }, + { privateAPIIdentifier: 'call.msgpack.encode' }, + { privateAPIIdentifier: 'call.http.doUri', helper: 'getJWT' }, + ]; return usageDtos.filter( (usageDto) =>