Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6cebb50
Add operation timeouts to sandbox provisioning and teardown
paddybyers May 3, 2026
6730b98
Increase integration test suite timeouts from 60s to 120s
paddybyers May 3, 2026
2953ebe
Fix RSL2b3 history time range test to use server timestamps
paddybyers May 3, 2026
89ad6a2
Restructure UTS tests to match spec repo unit/integration layout
paddybyers May 3, 2026
e0c1345
Add proxy integration test infrastructure and tests
paddybyers May 3, 2026
774ed58
Add tier 1+2 proxy integration tests
paddybyers May 3, 2026
f9f6166
Add ably-js tests for missing UTS spec coverage
paddybyers May 3, 2026
b61e070
Auto-launch Go test proxy from Node.js
paddybyers May 3, 2026
0302d6b
Add RSC15f test: expired fallback not resurrected by late in-flight s…
paddybyers May 3, 2026
743e8a3
Add RTN15a test variant for TCP close without WebSocket close frame
paddybyers May 4, 2026
910c107
Download uts-proxy binary from GitHub releases instead of building fr…
paddybyers May 4, 2026
7444c39
Remove deviations.md from repo
paddybyers May 4, 2026
0e3b974
Fix RSA4c3 tests: auth failure while CONNECTED should not set errorRe…
paddybyers May 5, 2026
3f87a4b
Fix RTN24 test: connectionId is not inside connectionDetails
paddybyers May 5, 2026
09a3ac9
Fix TM4 test: spec requires constructors, not toJSON
paddybyers May 5, 2026
73f6568
Fix batch operation test mocks to match server response format (RSC24…
paddybyers May 5, 2026
ac8a387
Fix RSC15l mock, add proxy integration tests for REST fallback (RSC15…
paddybyers May 5, 2026
184c10f
Add comprehensive REST proxy integration tests for HTTP error handling
paddybyers May 6, 2026
71a0297
Add UTS test IDs to all tests and implement 50 missing test scenarios
paddybyers May 6, 2026
3eaa563
Align unit test endpoints with UTS specs
paddybyers May 7, 2026
b94191d
Run data-path integration tests with both JSON and msgpack (G1)
paddybyers May 8, 2026
eacbc38
Add RSL6a3 msgpack interoperability tests
paddybyers May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
"test:react": "vitest run",
"test:package": "grunt test:package",
"test:uts": "npm run build:node && mocha --no-config --require tsx/cjs 'test/uts/**/*.test.ts'",
"test:uts:unit": "npm run build:node && mocha --no-config --require tsx/cjs --ignore 'test/uts/**/proxy/**' --ignore 'test/uts/**/integration/**' 'test/uts/**/*.test.ts'",
"concat": "grunt concat",
"build": "grunt build:all && npm run build:react",
"build:node": "grunt build:node",
Expand Down
239 changes: 117 additions & 122 deletions test/uts/deviations.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions test/uts/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DefaultRest } from '../../src/common/lib/client/defaultrest';
import { DefaultRealtime } from '../../src/common/lib/client/defaultrealtime';
import ErrorInfo from '../../src/common/lib/types/errorinfo';
import { makeFromDeserializedWithDependencies as makeProtocolMessageFromDeserialized } from '../../src/common/lib/types/protocolmessage';
import { populateFieldsFromParent } from '../../src/common/lib/types/basemessage';

const Ably = {
Rest: DefaultRest,
Expand Down Expand Up @@ -280,4 +281,5 @@ export {
trackClient,
restoreAll,
flushAsync,
populateFieldsFromParent,
};
32 changes: 32 additions & 0 deletions test/uts/helpers/protocol_variants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Protocol variant helpers for G1 compliance.
*
* Data-path integration tests should use describeEachProtocol() to run
* once per supported protocol (JSON and MessagePack).
*/

export type Protocol = 'json' | 'msgpack';

const PROTOCOLS: Protocol[] = ['json', 'msgpack'];

/**
* Wraps a describe block to run once per protocol variant.
* Produces test output like:
* suite name [json]
* ✓ test
* suite name [msgpack]
* ✓ test
*
* The callback receives mocha's Suite `this` context via `.call()`,
* so `this.timeout()` works inside the callback when using `function()` syntax.
*/
export function describeEachProtocol(
name: string,
fn: (this: Mocha.Suite, protocol: Protocol) => void,
): void {
for (const protocol of PROTOCOLS) {
describe(`${name} [${protocol}]`, function (this: Mocha.Suite) {
fn.call(this, protocol);
});
}
}
6 changes: 5 additions & 1 deletion test/uts/mock_http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class PendingRequest {
/** Request times out after connection established */
respond_with_timeout(): void {
this._resolve!({
error: { code: 408, statusCode: 408, message: 'Request timed out' } as any,
error: { code: 'ETIMEDOUT', statusCode: 408, message: 'Request timed out' } as any,
body: null,
headers: {},
unpacked: false,
Expand Down Expand Up @@ -320,6 +320,10 @@ class MockHttpClient {
) {
return true;
}
// RSC15l2: request timeout (HTTP 408)
if (statusCode === 408) {
return true;
}
return statusCode >= 500 && statusCode <= 504;
}
}
Expand Down
43 changes: 43 additions & 0 deletions test/uts/realtime/integration/auth/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('uts/realtime/integration/auth/auth', function () {
/**
* RSA8 - Token auth on realtime connection
*/
// UTS: realtime/integration/RSA8/token-auth-connect-0
it('RSA8 - JWT token auth connects successfully', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());

Expand All @@ -59,6 +60,7 @@ describe('uts/realtime/integration/auth/auth', function () {
/**
* RTC8a - In-band reauthorization on CONNECTED client
*/
// UTS: realtime/integration/RTC8a/in-band-reauth-connected-0
it('RTC8a - authorize on connected client does not disconnect', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());

Expand Down Expand Up @@ -93,6 +95,7 @@ describe('uts/realtime/integration/auth/auth', function () {
/**
* RTC8c - authorize() from INITIALIZED initiates connection
*/
// UTS: realtime/integration/RTC8c/authorize-initiates-connection-0
it('RTC8c - authorize from initialized state initiates connection', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());

Expand Down Expand Up @@ -120,6 +123,7 @@ describe('uts/realtime/integration/auth/auth', function () {
/**
* RSA7 - Matching clientId succeeds
*/
// UTS: realtime/integration/RSA7/matching-clientid-succeeds-0
it('RSA7 - matching clientId in JWT and options succeeds', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());
const testClientId = `test-client-${Math.random().toString(36).substring(2, 8)}`;
Expand All @@ -142,4 +146,43 @@ describe('uts/realtime/integration/auth/auth', function () {

await closeAndWait(client);
});

/**
* RSA7 - Mismatched clientId in JWT and options fails
*
* When the clientId in the JWT token differs from the clientId in
* ClientOptions, the server rejects the connection.
*/
// UTS: realtime/integration/RSA7/mismatched-clientid-fails-1
it('RSA7 - mismatched clientId fails', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());

const client = new Ably.Realtime({
authCallback: (_params: any, cb: any) => {
cb(null, generateJWT({ keyName, keySecret, clientId: 'token-client-id', ttl: 3600000 }));
},
clientId: 'wrong-client-id',
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
});
trackClient(client);

try {
await connectAndWait(client);
expect.fail('Expected connection to fail');
} catch (error: any) {
expect(error.message).to.include('failed');
}

expect(client.connection.state).to.equal('failed');
expect(client.connection.errorReason).to.not.be.null;
expect(client.connection.errorReason.code).to.equal(40102);

try {
await closeAndWait(client);
} catch (e) {
/* ok — already failed */
}
});
});
3 changes: 2 additions & 1 deletion test/uts/realtime/integration/auth/token_renewal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../sandbox';

describe('uts/realtime/integration/auth/token_renewal', function () {
this.timeout(60000);
this.timeout(120000);

before(async function () {
await setupSandbox();
Expand All @@ -34,6 +34,7 @@ describe('uts/realtime/integration/auth/token_renewal', function () {
/**
* RSA4b, RTN14b - Token renewal on expiry
*/
// UTS: realtime/integration/RSA4b/token-renewal-on-expiry-0
it('RSA4b/RTN14b - token renewal on expiry', async function () {
const { keyName, keySecret } = getKeyParts(getApiKey());
let callbackCount = 0;
Expand Down
2 changes: 2 additions & 0 deletions test/uts/realtime/integration/auth/token_request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('uts/realtime/integration/auth/token_request', function () {
/**
* RSA9a, RSA9g - createTokenRequest produces server-accepted token
*/
// UTS: realtime/integration/RSA9a/token-request-server-accepted-0
it('RSA9a/RSA9g - createTokenRequest produces server-accepted token', async function () {
const creator = new Ably.Rest({
key: getApiKey(),
Expand Down Expand Up @@ -64,6 +65,7 @@ describe('uts/realtime/integration/auth/token_request', function () {
/**
* RSA9 - createTokenRequest with clientId
*/
// UTS: realtime/integration/RSA9/token-request-with-clientid-0
it('RSA9 - createTokenRequest with clientId', async function () {
const testClientId = `token-request-client-${Math.random().toString(36).substring(2, 10)}`;

Expand Down
3 changes: 3 additions & 0 deletions test/uts/realtime/integration/channels/channel_attach.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('uts/realtime/integration/channels/channel_attach', function () {
/**
* RTL4c - Attach succeeds
*/
// UTS: realtime/integration/RTL4c/attach-succeeds-0
it('RTL4c - attach succeeds', async function () {
const channelName = uniqueChannelName('attach-RTL4c');

Expand Down Expand Up @@ -59,6 +60,7 @@ describe('uts/realtime/integration/channels/channel_attach', function () {
/**
* RTL5d - Detach succeeds
*/
// UTS: realtime/integration/RTL5d/detach-succeeds-0
it('RTL5d - detach succeeds', async function () {
const channelName = uniqueChannelName('detach-RTL5d');

Expand Down Expand Up @@ -86,6 +88,7 @@ describe('uts/realtime/integration/channels/channel_attach', function () {
/**
* RTL14 - Insufficient capability causes publish failure
*/
// UTS: realtime/integration/RTL14/insufficient-capability-failed-0
it('RTL14 - publish with subscribe-only key fails with 40160', async function () {
const channelName = uniqueChannelName('publish-not-allowed');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
uniqueChannelName,
pollUntil,
} from '../sandbox';
import { describeEachProtocol } from '../../../helpers/protocol_variants';

describe('uts/realtime/integration/channels/channel_history', function () {
describeEachProtocol('uts/realtime/integration/channels/channel_history', function (protocol) {
this.timeout(30000);

before(async function () {
Expand All @@ -33,22 +34,23 @@ describe('uts/realtime/integration/channels/channel_history', function () {
/**
* RTL10d - History contains messages published by another client
*/
// UTS: realtime/integration/RTL10d/history-cross-client-0
it('RTL10d - history contains messages from another client', async function () {
const channelName = uniqueChannelName('history-RTL10d');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down
28 changes: 17 additions & 11 deletions test/uts/realtime/integration/channels/channel_publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
uniqueChannelName,
pollUntil,
} from '../sandbox';
import { describeEachProtocol } from '../../../helpers/protocol_variants';

describe('uts/realtime/integration/channels/channel_publish', function () {
describeEachProtocol('uts/realtime/integration/channels/channel_publish', function (protocol) {
this.timeout(30000);

before(async function () {
Expand All @@ -33,22 +34,23 @@ describe('uts/realtime/integration/channels/channel_publish', function () {
/**
* RTL6, RSL4d2 - String data round-trip
*/
// UTS: realtime/integration/RTL6/string-data-roundtrip-0
it('RTL6/RSL4d2 - string data round-trip', async function () {
const channelName = uniqueChannelName('publish-string');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down Expand Up @@ -81,22 +83,23 @@ describe('uts/realtime/integration/channels/channel_publish', function () {
/**
* RTL6, RSL4d3 - JSON object data round-trip
*/
// UTS: realtime/integration/RTL6/json-data-roundtrip-1
it('RTL6/RSL4d3 - JSON object data round-trip', async function () {
const channelName = uniqueChannelName('publish-json');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down Expand Up @@ -132,22 +135,23 @@ describe('uts/realtime/integration/channels/channel_publish', function () {
/**
* RTL6, RSL4d1 - Binary data round-trip
*/
// UTS: realtime/integration/RTL6/binary-data-roundtrip-2
it('RTL6/RSL4d1 - binary data round-trip', async function () {
const channelName = uniqueChannelName('publish-binary');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down Expand Up @@ -182,22 +186,23 @@ describe('uts/realtime/integration/channels/channel_publish', function () {
/**
* RTL6f - connectionId matches publisher
*/
// UTS: realtime/integration/RTL6f/connectionid-matches-publisher-0
it('RTL6f - connectionId matches publisher', async function () {
const channelName = uniqueChannelName('publish-connid');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down Expand Up @@ -230,22 +235,23 @@ describe('uts/realtime/integration/channels/channel_publish', function () {
/**
* RSL6a2 - Message extras round-trip
*/
// UTS: realtime/integration/RSL6a2/message-extras-roundtrip-0
it('RSL6a2 - message extras round-trip', async function () {
const channelName = uniqueChannelName('pushenabled:publish-extras');

const publisher = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(publisher);

const subscriber = new Ably.Realtime({
key: getApiKey(),
endpoint: SANDBOX_ENDPOINT,
autoConnect: false,
useBinaryProtocol: false,
useBinaryProtocol: protocol === 'msgpack',
});
trackClient(subscriber);

Expand Down
Loading
Loading