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 b877c1c
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"`|
@@ -66,6 +68,33 @@ The assertions are sound; the points they are filed under are not.
66
68
67
69
The cost is coverage: TI2, TI3 and TI5 *as specified* are untested by the suite.
68
70
71
+
### Generated message IDs are asserted to be URL-safe base64
72
+
73
+
`RSL1k2/message-id-format-0` and `RSAN1c4/idempotent-id-generated-0` assert
74
+
`[A-Za-z0-9_-]+`. `features.md` RSL1k1 requires only "base64-encoding a sequence of
75
+
at least 9 bytes" and names no alphabet, so the specification is stricter than its
76
+
source and rejects a conforming SDK's ids at random. ably-js encodes with the
77
+
standard alphabet and would fail both.
78
+
79
+
ably-python now encodes URL-safe, so both tests are derived and pass. That settles
80
+
the tests, not the specification: RSL1k1 still needs either to name the alphabet or
81
+
to let the tests accept both.
82
+
83
+
### Wire encodings contradict the features spec
84
+
85
+
-`publish.md` RSL1c asserts `body[1]["data"] == {"key": "value"}`. RSL4c3 and RSL4d3
86
+
require an object payload to be JSON-stringified with `encoding` set to `"json"`.
87
+
-`publish.md` RSL1e asserts exact equality against a whole message body, which
88
+
cannot hold while idempotent publishing (RSL1k1, on by default) adds an `id`.
89
+
-`RSL1k/mixed-ids-in-batch-1` expects a generated id for the id-less message in a
90
+
mixed batch. RSL1k1 generates ids only when every message lacks one, and RSL1k3
91
+
requires ids "present or absent" to be preserved.
92
+
-`batch_publish.md` RSC22c6 asserts base64 with `encoding: "base64"` for binary
93
+
payloads, which is the RSL4d1 JSON-protocol branch. The test never sets
94
+
`useBinaryProtocol: false` and TO3f defaults it true, so RSL4c1 governs.
95
+
-`auth_scheme.md` asserts a raw `Bearer <token>`. RSA3b makes Base64 optional, and
96
+
ably-python and ably-js both encode.
97
+
69
98
### Fixtures that cannot hold their stated values
70
99
71
100
-`RSP5/decode-cipher-channel-7`: the ciphertext is 32 bytes, an IV plus one AES-CBC
@@ -128,9 +157,10 @@ the mark is the only change needed once the SDK behaviour lands.
128
157
| Spec points | Missing | Tests |
129
158
|---|---|---|
130
159
160
+
| 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 |
131
161
| RSP3a2, RSP3a3 |`clientId` and `connectionId` filters on `RestPresence#get`. `Presence.get` takes only `limit`, while `Presence.history` does take its documented params | 3 |
132
162
| TP5 |`size` on `PresenceMessage`. The related `maxMessageSize` gap is adapted rather than gated, below; `features.md` TM6 has no UTS test | 1 |
133
-
163
+
| 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 |
134
164
| 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 |
135
165
| 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 |
136
166
| 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 |
@@ -154,10 +184,13 @@ comment above. These run, so they guard against regression.
154
184
155
185
| Spec points | Specification | ably-python | Status |
156
186
|---|---|---|---|
157
-
187
+
| 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 |
188
+
| 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 |
189
+
| 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 |
158
190
| RSC1b | Error code 40106 | A bare `ValueError` from `AblyRest.__init__` with an informative message, not an `AblyException`, so there is no code | Open bug |
159
191
| 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 |
160
192
| REC1b1, REC1c1 | Code 40000, or a message containing "invalid" or "conflict" | 400/40106 with a specific message. The features spec mandates no code | Cosmetic |
193
+
| RSAN1a3 | Code 40003 for a missing `Annotation.type`| 400/40000 | Cosmetic; worth aligning cross-SDK |
161
194
162
195
| HP6 |`errorCode` is a number | The raw header string, `'40101'`| Open bug, trivial |
163
196
| 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 |
@@ -166,6 +199,8 @@ comment above. These run, so they guard against regression.
166
199
| 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 |
167
200
| 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 |
168
201
202
+
| 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 |
203
+
| 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 |
169
204
| 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 |
170
205
| 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