Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion specifications/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,8 @@ The threading and/or asynchronous model for each realtime library will vary by l
- `(RTN16k)` When the library first connects to Ably after being instantiated with a `recover` client option, it should add an additional `recover` querystring param to the websocket request, set from the `connectionKey` component of the `recoveryKey`. Once the library has successfully connected to Ably, it should never again supply a `recover` querystring param.
- `(RTN16g)` `Connection#createRecoveryKey` is a function that returns a string which incorporates the `connectionKey`, the current `msgSerial`, and a collection of pairs of channel `name` and current `channelSerial` for every currently attached channel.
- `(RTN16g1)` It must be serialized in a way which is able to encode any unicode channel name. The SDK may assume that the recovery key will only be consumed by the same type of SDK, so this spec does not specify any particular serialization; however, the format should be forward-compatible through the same major version of the SDK.
- `(RTN16g2)` It should return `Null` when the SDK is in the `CLOSED`, `CLOSING`, `FAILED`, or `SUSPENDED` states, or when it does not have a `connectionKey` (for example, it has not yet become connected).
- `(RTN16g2)` This clause has been replaced by [`RTN16g3`](#RTN16g3) as of specification version 6.1.0.
- `(RTN16g3)` It should return `Null` when the SDK is in the `CLOSED`, `CLOSING`, or `FAILED` states, or when it does not have a `connectionKey` (for example, it has not yet become connected).
- `(RTN16d)` The library may wish to test that after a connection has been successfully recovered, the `Connection#id` should be identical to the `id` of the connection that was recovered, and `Connection#key` should have been updated to the `ConnectionDetails#connectionKey` provided in the `CONNECTED` `ProtocolMessage`.
- `(RTN16m)` This clause has been deleted. It was valid up to and including specification version `2.1`.
- `(RTN16m1)` This clause has been deleted. It was valid up to and including specification version `2.1`.
Expand Down
12 changes: 6 additions & 6 deletions uts/docs/completion-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ This matrix lists all spec items from the [Ably features spec](../../specificati
| RTN5 | Concurrency test (50+ clients) | |
| RTN6 | Successful connection definition | Information only|
| RTN7 | ACK and NACK handling (RTN7a–RTN7e) | Yes — `realtime/unit/channels/channel_publish.md` covers RTN7a, RTN7b (via RTL6j tests), RTN7d, RTN7e |
| RTN8 | Connection#id attribute (RTN8a–RTN8c) | Yes — `realtime/unit/connection/connection_id_key_test.md` |
| RTN9 | Connection#key attribute (RTN9a–RTN9c) | Yes — `realtime/unit/connection/connection_id_key_test.md` |
| RTN8 | Connection#id attribute (RTN8a–RTN8d) | Yes — `realtime/unit/connection/connection_id_key_test.md` |
| RTN9 | Connection#key attribute (RTN9a–RTN9d) | Yes — `realtime/unit/connection/connection_id_key_test.md` |
| RTN11 | Connect function (RTN11a–RTN11f) | Partial — `realtime/integration/connection_lifecycle_test.md` covers RTN11; `realtime/unit/connection/error_reason_test.md` covers RTN11d |
| RTN12 | Close function (RTN12a–RTN12f) | Partial — `realtime/integration/connection_lifecycle_test.md` covers RTN12, RTN12a |
| RTN13 | Ping function (RTN13a–RTN13e) | Yes — `realtime/unit/connection/connection_ping_test.md` |
| RTN14 | Connection opening failures (RTN14a–RTN14g) | Yes — `realtime/unit/connection/connection_open_failures_test.md`; `realtime/integration/connection/connection_failures_test.md` covers RTN14a, RTN14g; `realtime/integration/auth/token_renewal_test.md` covers RTN14b; `realtime/integration/proxy/connection_open_failures.md` covers RTN14a–RTN14d, RTN14g |
| RTN15 | Connection failures when CONNECTED (RTN15a–RTN15j) | Yes — `realtime/unit/connection/connection_failures_test.md`; `realtime/integration/proxy/connection_resume.md` covers RTN15a, RTN15b, RTN15c6, RTN15c7, RTN15g, RTN15g2, RTN15h1, RTN15h3, RTN15j |
| RTN16 | Connection recovery (RTN16a–RTN16m1) | Yes — `realtime/unit/connection/connection_recovery_test.md` covers RTN16d, RTN16f, RTN16f1, RTN16g, RTN16g1, RTN16g2, RTN16i, RTN16j, RTN16k, RTN16l; `realtime/integration/proxy/connection_resume.md` covers RTN16d, RTN16l; `realtime/unit/connection/error_reason_test.md` covers RTN16e |
| RTN14 | Connection opening failures (RTN14a–RTN14h) | Yes — `realtime/unit/connection/connection_open_failures_test.md`; `realtime/integration/connection/connection_failures_test.md` covers RTN14a, RTN14g; `realtime/integration/auth/token_renewal_test.md` covers RTN14b; `realtime/integration/proxy/connection_open_failures.md` covers RTN14a–RTN14d, RTN14g; `realtime/unit/connection/connection_failures_test.md` and `realtime/integration/proxy/connection_resume.md` cover RTN14h |
| RTN15 | Connection failures when CONNECTED (RTN15a–RTN15j) | Yes — `realtime/unit/connection/connection_failures_test.md`; `realtime/integration/proxy/connection_resume.md` covers RTN15a, RTN15b, RTN15c6, RTN15c7, RTN15h1, RTN15h3, RTN15j |
| RTN16 | Connection recovery (RTN16a–RTN16m1) | Yes — `realtime/unit/connection/connection_recovery_test.md` covers RTN16d, RTN16f, RTN16f1, RTN16g, RTN16g1, RTN16g3, RTN16i, RTN16j, RTN16k, RTN16l; `realtime/integration/proxy/connection_resume.md` covers RTN16d, RTN16l; `realtime/unit/connection/error_reason_test.md` covers RTN16e |
| RTN17 | Domain selection and fallback (RTN17a–RTN17j) | Yes — `realtime/unit/connection/fallback_hosts_test.md` |
| RTN19 | Transport state side effects (RTN19a–RTN19b) | Yes — `realtime/unit/channels/channel_publish.md` covers RTN19a, RTN19a2, RTN19b; `realtime/integration/proxy/connection_resume.md` covers RTN19a, RTN19a2 |
| RTN20 | OS network change handling (RTN20a–RTN20c) | Yes — `realtime/unit/connection/network_change_test.md` |
Expand Down Expand Up @@ -398,7 +398,7 @@ This matrix lists all spec items from the [Ably features spec](../../specificati
| RTN15a | Unexpected disconnect triggers resume | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15b/c6 | Resume preserves connectionId | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15c7 | Failed resume gets new connectionId | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15g/g2 | connectionStateTtl expiry clears resume state | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN14h | Reconnection still resumes after connectionStateTtl expiry | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15h1 | DISCONNECTED with token error, non-renewable → FAILED | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15h3 | DISCONNECTED with non-token error → reconnect | Yes — `realtime/integration/proxy/connection_resume.md` |
| RTN15j | Fatal ERROR on established connection | Yes — `realtime/integration/proxy/connection_resume.md` |
Expand Down
32 changes: 17 additions & 15 deletions uts/realtime/integration/proxy/connection_resume.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Connection Resume and Recovery Proxy Integration Tests (RTN15, RTN16)

Spec points: `RTN15a`, `RTN15b`, `RTN15c6`, `RTN15c7`, `RTN15g`, `RTN15g2`, `RTN15h1`, `RTN15h3`, `RTN15j`, `RTN16d`, `RTN16l`, `RTN19a`, `RTN19a2`
Spec points: `RTN14h`, `RTN15a`, `RTN15b`, `RTN15c6`, `RTN15c7`, `RTN15h1`, `RTN15h3`, `RTN15j`, `RTN16d`, `RTN16l`, `RTN19a`, `RTN19a2`

## Test Type

Expand Down Expand Up @@ -759,18 +759,17 @@ session.close()

---

## Test 22: RTN15g/g2 - connectionStateTtl expiry clears resume state
## Test 22: RTN14h - reconnection still resumes after connectionStateTtl expiry

**Test ID**: `realtime/proxy/RTN15g/ttl-expiry-clears-resume-0`
**Test ID**: `realtime/proxy/RTN14h/resume-after-ttl-expiry-0`

| Spec | Requirement |
|------|-------------|
| RTN15g | If disconnected for longer than connectionStateTtl, do not attempt resume; connect as a fresh connection |
| RTN15g2 | The staleness measure is whether the time since last activity exceeds connectionStateTtl + maxIdleInterval |
| RTN14h | Reconnection attempts in the SUSPENDED state should continue to attempt to resume, regardless of how long it has been since the client was last connected |

Tests that when the client has been disconnected for longer than connectionStateTtl + maxIdleInterval, the SDK does not attempt to resume. Instead it makes a fresh connection, resulting in a new connectionId.
As of specification version 6.1.0 this replaces RTN15g (deleted). The client no longer clears its connection state based on how long it has been disconnected: it always attempts to resume and lets the server decide whether continuity can be preserved. This test verifies that the reconnection attempt made after the connectionStateTtl has expired and the connection has become SUSPENDED still carries the `resume` query param. (The real server will typically have discarded the connection state by then, so the resume fails server-side and the client ends up with a new connectionId — but the client still attempts the resume.)

**Unit test counterpart:** `connection_failures_test.md` > RTN15g
**Unit test counterpart:** `connection_failures_test.md` > RTN14h

### Setup

Expand Down Expand Up @@ -805,19 +804,19 @@ session = create_proxy_session(
}
},
"times": 1,
"comment": "RTN15g: Replace 1st CONNECTED to set short connectionStateTtl (2s) and known connectionId"
"comment": "RTN14h: Replace 1st CONNECTED to set short connectionStateTtl (2s) and known connectionId"
},
{
"match": { "type": "delay_after_ws_connect", "delayMs": 1000 },
"action": { "type": "close" },
"times": 1,
"comment": "RTN15g: Close connection after 1s — client enters DISCONNECTED with 2s TTL"
"comment": "RTN14h: Close connection after 1s — client enters DISCONNECTED with 2s TTL"
},
{
"match": { "type": "ws_connect", "count": 2 },
"action": { "type": "refuse_connection" },
"times": 1,
"comment": "RTN15g: Refuse 2nd ws_connect — keeps client disconnected until TTL expires and SUSPENDED fires"
"comment": "RTN14h: Refuse 2nd ws_connect — keeps client disconnected until TTL expires and SUSPENDED fires"
}
]
)
Expand Down Expand Up @@ -853,8 +852,9 @@ ASSERT client.connection.id == "proxy-ttl-test-id"
# T=1s: proxy closes connection -> DISCONNECTED
# T=1-3s: retry attempt is refused -> stays DISCONNECTED
# T=3s: connectionStateTtl(2s) expires -> SUSPENDED
# T=4s: suspendedRetryTimeout(1s) fires -> fresh ws_connect (no resume)
# -> CONNECTED with new connectionId from real server
# T=4s: suspendedRetryTimeout(1s) fires -> ws_connect that still attempts a
# resume (RTN14h); the server has discarded the state, so it responds
# with a new connectionId -> CONNECTED

AWAIT_STATE client.connection.state == ConnectionState.suspended
WITH timeout: 15s
Expand All @@ -867,7 +867,8 @@ AWAIT_STATE client.connection.state == ConnectionState.connected
### Assertions

```pseudo
# RTN15g: Connection ID changed (fresh connection, not resumed)
# The server discarded the connection state, so the resume failed server-side
# and the connection ID changed.
ASSERT client.connection.id != "proxy-ttl-test-id"

# Verify the proxy log shows at least 3 ws_connects
Expand All @@ -878,9 +879,10 @@ ASSERT ws_connects.length >= 3
# First ws_connect: initial — no resume
ASSERT ws_connects[0].queryParams["resume"] IS null

# Last ws_connect: fresh connection after TTL expiry — no resume
# RTN14h: the reconnection made after the TTL expired and the connection became
# suspended still attempts a resume, so it carries the resume query param.
last_ws_connect = ws_connects[ws_connects.length - 1]
ASSERT last_ws_connect.queryParams["resume"] IS null
ASSERT last_ws_connect.queryParams["resume"] IS NOT null
```

### Cleanup
Expand Down
17 changes: 8 additions & 9 deletions uts/realtime/unit/channels/channel_attach.md
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ CLOSE_CLIENT(client)

**Spec requirement:** The ATTACH ProtocolMessage channelSerial field must be set to the RTL15b channelSerial. If the RTL15b channelSerial is not set, the field may be set to null or omitted.

Tests that channelSerial is included in ATTACH message when available. Uses setOptions (RTL16a) to trigger a reattach without going through DETACHED state, since RTL15b1 clears channelSerial on DETACHED.
Tests that channelSerial is included in ATTACH message when available. Uses setOptions (RTL16a) to trigger a reattach without going through DETACHED state, since RTL15b2 clears channelSerial on DETACHED.

### Setup
```pseudo
Expand Down Expand Up @@ -678,7 +678,7 @@ AWAIT_STATE client.connection.state == ConnectionState.connected
AWAIT channel.attach()

# Trigger reattach via setOptions (RTL16a) — does NOT go through DETACHED,
# so channelSerial is preserved (RTL15b1 only clears on DETACHED/SUSPENDED/FAILED)
# so channelSerial is preserved (RTL15b2 only clears on DETACHED/FAILED)
AWAIT channel.setOptions(ChannelOptions(modes: [subscribe]))
```

Expand Down Expand Up @@ -902,13 +902,13 @@ CLOSE_CLIENT(client)

---

## RTL4j - ATTACH_RESUME flag set for reattach
## RTL4j - ATTACH_RESUME flag no longer set

**Test ID**: `realtime/unit/RTL4j/attach-resume-flag-0`
**Test ID**: `realtime/unit/RTL4j/attach-resume-flag-not-set-0`

**Spec requirement:** If the attach is not a clean attach, the library should set the ATTACH_RESUME flag in the ATTACH message. Per RTL4j1, `attachResume` is cleared when the channel enters DETACHING or FAILED, so a detach+reattach IS a clean attach and should NOT have ATTACH_RESUME. A reattach while still attached (e.g. via setOptions) is NOT a clean attach and SHOULD have ATTACH_RESUME.
**Spec requirement:** As of specification version 6.1.0, RTL4j has been deleted: the server now makes the resumability decision, so the client need not set the (deprecated) ATTACH_RESUME flag (TR3f) on any ATTACH, including reattaches of a previously-attached channel.

Tests that ATTACH_RESUME flag is set on reattach while attached, but not on a clean attach.
Tests that the ATTACH_RESUME flag is set on neither a clean attach nor a reattach while attached.

### Setup
```pseudo
Expand Down Expand Up @@ -947,9 +947,8 @@ AWAIT channel.setOptions(params: {rewind: "1"})
### Assertions
```pseudo
ASSERT length(captured_attach_messages) == 2
# First attach should NOT have ATTACH_RESUME flag
# Neither ATTACH should have the ATTACH_RESUME flag set
ASSERT (captured_attach_messages[0].flags AND 32) == 0 # ATTACH_RESUME = 32
# Second attach (reattach while attached) SHOULD have ATTACH_RESUME flag
ASSERT (captured_attach_messages[1].flags AND 32) != 0 # ATTACH_RESUME = 32
ASSERT (captured_attach_messages[1].flags AND 32) == 0 # ATTACH_RESUME = 32
CLOSE_CLIENT(client)
```
35 changes: 19 additions & 16 deletions uts/realtime/unit/channels/channel_properties.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Channel Properties Tests

Spec points: `RTL15`, `RTL15b`, `RTL15b1`, `RTL15c`
Spec points: `RTL15`, `RTL15b`, `RTL15b1`, `RTL15b2`, `RTL15c`

## Test Type
Unit test with mocked WebSocket
Expand Down Expand Up @@ -448,23 +448,23 @@ AWAIT_STATE channel.state == ChannelState.attached
```pseudo
# channelSerial should be from the new ATTACHED, not from DETACHED
# The DETACHED action should not have updated channelSerial
# (RTL15b1 clears it on DETACHED/SUSPENDED/FAILED, then ATTACHED sets it fresh)
# (RTL15b2 clears it on DETACHED/FAILED, then ATTACHED sets it fresh)
ASSERT attach_count == 2
ASSERT channel.properties.channelSerial == "serial-001"
CLOSE_CLIENT(client)
```

---

## RTL15b1 - channelSerial cleared on DETACHED state
## RTL15b2 - channelSerial cleared on DETACHED state

**Test ID**: `realtime/unit/RTL15b1/serial-cleared-detached-0`
**Test ID**: `realtime/unit/RTL15b2/serial-cleared-detached-0`

| Spec | Requirement |
|------|-------------|
| RTL15b1 | If the channel enters the DETACHED, SUSPENDED, or FAILED state, it must clear its channelSerial |
| RTL15b2 | If the channel enters the DETACHED or FAILED state, it must clear its channelSerial (unlike previous spec versions, it must not clear it when entering SUSPENDED) |

Tests that `channelSerial` is cleared when the channel transitions to DETACHED.
Tests that `channelSerial` is cleared when the channel transitions to DETACHED. (As of specification version 6.1.0, RTL15b1 has been replaced by RTL15b2.)

### Setup
```pseudo
Expand Down Expand Up @@ -515,17 +515,19 @@ CLOSE_CLIENT(client)

---

## RTL15b1 - channelSerial cleared on SUSPENDED state
## RTL15b2 - channelSerial retained in SUSPENDED state

**Test ID**: `realtime/unit/RTL15b1/serial-cleared-suspended-1`
**Test ID**: `realtime/unit/RTL15b2/serial-retained-suspended-1`

**Spec requirement:** If the channel enters the SUSPENDED state, it must clear its `channelSerial`.
**Spec requirement:** If the channel enters the SUSPENDED state, it must NOT clear its `channelSerial`.

Tests that `channelSerial` is cleared when the channel transitions to SUSPENDED (e.g. due to attach timeout).
As of specification version 6.1.0 (RTL15b2, replacing RTL15b1) the channelSerial is cleared only when entering DETACHED or FAILED. It is retained through SUSPENDED, so that it can be included on the subsequent ATTACH (RTL4c1) for the server's continuity decision.

Tests that `channelSerial` is retained when the channel transitions to SUSPENDED (e.g. due to attach timeout).

### Setup
```pseudo
channel_name = "test-RTL15b1-suspended-${random_id()}"
channel_name = "test-RTL15b2-suspended-${random_id()}"
attach_count = 0

mock_ws = MockWebSocket(
Expand Down Expand Up @@ -578,23 +580,24 @@ AWAIT_STATE channel.state == ChannelState.suspended
### Assertions
```pseudo
ASSERT channel.state == ChannelState.suspended
ASSERT channel.properties.channelSerial IS null
# RTL15b2: channelSerial is retained through SUSPENDED, not cleared
ASSERT channel.properties.channelSerial == "serial-001"
CLOSE_CLIENT(client)
```

---

## RTL15b1 - channelSerial cleared on FAILED state
## RTL15b2 - channelSerial cleared on FAILED state

**Test ID**: `realtime/unit/RTL15b1/serial-cleared-failed-2`
**Test ID**: `realtime/unit/RTL15b2/serial-cleared-failed-2`

**Spec requirement:** If the channel enters the FAILED state, it must clear its `channelSerial`.

Tests that `channelSerial` is cleared when the channel transitions to FAILED (e.g. due to channel ERROR).
Tests that `channelSerial` is cleared when the channel transitions to FAILED (e.g. due to channel ERROR). (As of specification version 6.1.0, RTL15b1 has been replaced by RTL15b2.)

### Setup
```pseudo
channel_name = "test-RTL15b1-failed-${random_id()}"
channel_name = "test-RTL15b2-failed-${random_id()}"

mock_ws = MockWebSocket(
onConnectionAttempt: (conn) => conn.respond_with_success(CONNECTED_MESSAGE),
Expand Down
Loading
Loading