You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0ba7ed7
Browse filesBrowse the repository at this point in the historyBrowse files
Covers channel/publish.md, publish_result.md, history.md, get_message.md,
update_delete_message.md, message_versions.md, annotations.md,
idempotency.md and rest_channel_attributes.md.
Three tests carry the deviation mark: the two covering RestChannel#setOptions,
which the REST channel's options setter cannot accept, and RSL1i, where a
REST publish never checks the message size. Two more assert what features.md
contradicts and carry the spec_error mark.
The adapted tests record the channel name encoding as it stands, including
the space that travels as `+` and the unescaped name in Channel#status.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: test/uts/deviations.md
+37-2Lines changed: 37 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,6 +33,8 @@ The tests gated this way:
33
33
34
34
| Test | Spec error |
35
35
|---|---|
36
+
|`test_rsl1a_publish_message_array`| RSL1c - an object payload asserted to travel unstringified |
37
+
|`test_rsl1k_mixed_ids_in_batch`| RSL1k - an absent id in a mixed batch asserted to be generated |
36
38
37
39
|`test_rsp4_history_pagination`| RSP4 - wire action 4 asserted to be LEAVE |
38
40
|`test_tp3_presence_to_json`| TP3 - an outgoing action asserted as the string `"enter"`|
@@ -86,6 +88,33 @@ The assertions are sound; the points they are filed under are not.
86
88
87
89
The cost is coverage: TI2, TI3 and TI5 *as specified* are untested by the suite.
88
90
91
+
### Generated message IDs are asserted to be URL-safe base64
92
+
93
+
`RSL1k2/message-id-format-0` and `RSAN1c4/idempotent-id-generated-0` assert
94
+
`[A-Za-z0-9_-]+`. `features.md` RSL1k1 requires only "base64-encoding a sequence of
95
+
at least 9 bytes" and names no alphabet, so the specification is stricter than its
96
+
source and rejects a conforming SDK's ids at random. ably-js encodes with the
97
+
standard alphabet and would fail both.
98
+
99
+
ably-python now encodes URL-safe, so both tests are derived and pass. That settles
100
+
the tests, not the specification: RSL1k1 still needs either to name the alphabet or
101
+
to let the tests accept both.
102
+
103
+
### Wire encodings contradict the features spec
104
+
105
+
-`publish.md` RSL1c asserts `body[1]["data"] == {"key": "value"}`. RSL4c3 and RSL4d3
106
+
require an object payload to be JSON-stringified with `encoding` set to `"json"`.
107
+
-`publish.md` RSL1e asserts exact equality against a whole message body, which
108
+
cannot hold while idempotent publishing (RSL1k1, on by default) adds an `id`.
109
+
-`RSL1k/mixed-ids-in-batch-1` expects a generated id for the id-less message in a
110
+
mixed batch. RSL1k1 generates ids only when every message lacks one, and RSL1k3
111
+
requires ids "present or absent" to be preserved.
112
+
-`batch_publish.md` RSC22c6 asserts base64 with `encoding: "base64"` for binary
113
+
payloads, which is the RSL4d1 JSON-protocol branch. The test never sets
114
+
`useBinaryProtocol: false` and TO3f defaults it true, so RSL4c1 governs.
115
+
-`auth_scheme.md` asserts a raw `Bearer <token>`. RSA3b makes Base64 optional, and
116
+
ably-python and ably-js both encode.
117
+
89
118
### Fixtures that cannot hold their stated values
90
119
91
120
-`RSP5/decode-cipher-channel-7`: the ciphertext is 32 bytes, an IV plus one AES-CBC
@@ -148,9 +177,10 @@ the mark is the only change needed once the SDK behaviour lands.
148
177
| Spec points | Missing | Tests |
149
178
|---|---|---|
150
179
180
+
| RSL7 |`RestChannel#setOptions`. The realtime channel implements it; the REST `options` setter expects the kwargs dict `Channels.get` collected, so a `ChannelOptions` raises `TypeError`| 2 |
151
181
| RSP3a2, RSP3a3 |`clientId` and `connectionId` filters on `RestPresence#get`. `Presence.get` takes only `limit`, while `Presence.history` does take its documented params | 3 |
152
182
| TP5 |`size` on `PresenceMessage`. The related `maxMessageSize` gap is adapted rather than gated, below; `features.md` TM6 has no UTS test | 1 |
153
-
183
+
| RSL1i | REST publish never calls `validate_message_size`. The helper exists and is correct, but only `ably/realtime/channel.py:423` calls it, so an oversized REST publish goes out | 1 |
154
184
| RSC2, RSC3, RSC4, TO3b, TO3c, TO3c2 |`log_handler` as a client option, and any use of `log_level` — it is stored on `Options` and read by nothing | 4 |
155
185
| TI4, TI1/TI5 |`href` anywhere in the SDK, and `cause` when deserialising. `AblyException.from_dict` and `raise_for_response` read only `message`, `statusCode` and `code`, so both fields are dropped from server errors | 2 |
156
186
| TP3a, TP3d, TP3g | Presence attributes defaulted from the encapsulating ProtocolMessage. There is no ProtocolMessage type; `ably/realtime/channel.py:751-761` passes the presence array through without context. Matters for synthesized-leave detection and `memberKey`| 3 |
@@ -174,10 +204,13 @@ comment above. These run, so they guard against regression.
174
204
175
205
| Spec points | Specification | ably-python | Status |
176
206
|---|---|---|---|
177
-
207
+
| RSL2 | A space in a channel name is `%20`|`+`, from `parse.quote_plus` in `Channel.__init__`. `quote_plus` is form encoding, and a `+` in a URL *path* is a literal plus, so the name reaching the server is altered | Open bug, and a genuine correctness issue |
208
+
| RSL8 |`Channel#status` URI-encodes the channel id |`status()` interpolates the name with no escaping at all. `a/b` addresses the wrong resource, `a?b` truncates the name into a query string, `a#b` becomes a fragment | Open bug |
209
+
| RSL2, RSL11b, RSL15b |`:` is `%3A`| Left literal, from `safe=':'`. RFC 3986 allows `:` in a path segment and Ably uses it for namespaces, so the server receives the same value | Intentional |
178
210
| RSC1b | Error code 40106 | A bare `ValueError` from `AblyRest.__init__` with an informative message, not an `AblyException`, so there is no code | Open bug |
179
211
| RSC18 | The constructor rejects basic auth over HTTP | Construction succeeds; 40103 is raised from `make_request` when a request needing Basic Auth is attempted, and no request goes out. RSA1/RSC18 say only "any attempt to use" | Compliant; the UTS is stricter than its source |
180
212
| REC1b1, REC1c1 | Code 40000, or a message containing "invalid" or "conflict" | 400/40106 with a specific message. The features spec mandates no code | Cosmetic |
213
+
| RSAN1a3 | Code 40003 for a missing `Annotation.type`| 400/40000 | Cosmetic; worth aligning cross-SDK |
181
214
182
215
| HP6 |`errorCode` is a number | The raw header string, `'40101'`| Open bug, trivial |
183
216
| HP8 |`headers` is a map | A list of `(name, value)` pairs, so the lookup the spec describes is impossible without converting, and case-insensitivity is lost | Open bug; changing the return type is breaking |
@@ -186,6 +219,8 @@ comment above. These run, so they guard against regression.
186
219
| TI |`ErrorInfo` equality by attributes | No `__eq__`, so errors compare by identity. Python exceptions conventionally do, and the requirement appears nowhere in `features.md`. Adding `__eq__` without `__hash__` would make `AblyException` unhashable and break any caller that puts one in a set | Intentional |
187
220
| TD5, RSA16a |`capability` is stringified JSON | A `Capability` object, a public convenience type used throughout `auth`. Narrowing the return type to `str` would break every caller that indexes or mutates it, so it is reserved for a future major | Intentional; a breaking change to align |
188
221
222
+
| CHM2 | Missing metrics default to 0 |`ChannelMetrics.from_dict` uses a bare `obj.get(name)`, so any omitted metric parses as `None`| Open bug, broader than CHM2g/h |
223
+
| CHM2g, CHM2h |`objectPublishers` and `objectSubscribers` on `ChannelMetrics`| Neither is modelled, so both are dropped on parsing. The test asserts their absence, and turns red once they are added | Open bug |
189
224
| TO3l8 |`maxMessageSize` is a client option, default 65536 | Rejected by `Options.__init__`. `ably/realtime/channel.py:422` reads it with `getattr(..., 65536)`, so the default holds but cannot be configured, nor overridden by `connectionDetails` (CD2c) | Open bug |
190
225
| TO3l1, TO3l5 |`httpRequestTimeout` and `httpMaxRetryCount` carry their defaults on the options object | Left unset; the effective defaults are applied downstream by `Http` and by `Options.__get_hosts`. The spec's values are milliseconds, while ably-python's `http_request_timeout` is seconds | Intentional |
0 commit comments