Skip to content

Commit 36c42b3

Browse files
committed
Fix integration test specs based on sandbox behavior
Update REST integration test specs (auth, mutable messages, revoke tokens) to align assertions with actual Ably sandbox behaviour.
1 parent e771f5e commit 36c42b3

3 files changed

Lines changed: 96 additions & 19 deletions

File tree

uts/rest/integration/auth.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,14 @@ Tests that invalid API keys are rejected by the server.
220220
### Setup
221221
```pseudo
222222
channel_name = "test-RSA4-invalid-" + random_id()
223+
224+
# Use the real app_id with a fabricated key to guarantee a 401 response.
225+
# Using a completely fake app ID (e.g. "invalid.key:secret") may return
226+
# 404 (app not found) instead of 401 (unauthorized), depending on the server.
227+
invalid_key = app_id + ".invalidKey:invalidSecret"
228+
223229
client = Rest(options: ClientOptions(
224-
key: "invalid.key:secret",
230+
key: invalid_key,
225231
endpoint: "sandbox"
226232
))
227233
```
@@ -314,18 +320,17 @@ client = Rest(options: ClientOptions(
314320

315321
### Test Steps
316322
```pseudo
317-
# Request to allowed channel should succeed
318-
allowed_result = AWAIT client.request("GET", "/channels/" + allowed_channel)
323+
# Publish to allowed channel should succeed — the JWT grants "publish" capability.
324+
# Note: Do NOT use client.request("GET", "/channels/...") here — that is a channel
325+
# status request which requires "channel-metadata" capability, not "publish".
326+
AWAIT client.channels.get(allowed_channel).publish(name: "test", data: "hello")
319327
320-
# Request to denied channel should fail with 40160 (capability refused)
321-
AWAIT client.request("POST", "/channels/" + denied_channel + "/messages",
322-
body: {"name": "test", "data": "hello"}
323-
) FAILS WITH error
328+
# Publish to denied channel should fail with 40160 (capability refused)
329+
AWAIT client.channels.get(denied_channel).publish(name: "test", data: "hello") FAILS WITH error
324330
```
325331

326332
### Assertions
327333
```pseudo
328-
ASSERT allowed_result.statusCode >= 200 AND allowed_result.statusCode < 300
329334
ASSERT error.code == 40160
330335
ASSERT error.statusCode == 401
331336
```

uts/rest/integration/mutable_messages.md

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@ AFTER ALL TESTS:
3333
- All clients use `endpoint: "sandbox"`
3434
- All channel names use the `mutable:` namespace prefix — the test app setup configures the `mutable` namespace with `mutableMessages: true`, which is required for getMessage, updateMessage, deleteMessage, appendMessage, and annotations
3535

36+
### Annotation HTTP Body Format
37+
38+
The annotation publish and delete endpoints (`POST /channels/{channel}/messages/{serial}/annotations`)
39+
expect the HTTP request body to be a **JSON array** containing a single annotation object:
40+
41+
```json
42+
[{"type": "com.ably.reactions", "name": "like", "action": 0}]
43+
```
44+
45+
Sending a bare object (not wrapped in an array) returns HTTP 400 "invalid request body".
46+
47+
The `action` field is **required** by the server and must be set by the SDK:
48+
- `0` = `ANNOTATION_CREATE` (for publish)
49+
- `1` = `ANNOTATION_DELETE` (for delete)
50+
51+
The SDK's `annotations.publish()` and `annotations.delete()` methods must set the
52+
`action` field and wrap the annotation in an array before sending.
53+
3654
---
3755

3856
## RSL1n — publish returns serials from sandbox
@@ -154,8 +172,14 @@ ASSERT update_result IS UpdateDeleteResult
154172
ASSERT update_result.versionSerial IS String
155173
ASSERT update_result.versionSerial.length > 0
156174
157-
# Verify via getMessage
158-
updated_msg = AWAIT channel.getMessage(serial)
175+
# Verify via getMessage — poll until the update is visible
176+
updated_msg = poll_until(
177+
condition: FUNCTION() =>
178+
msg = AWAIT channel.getMessage(serial)
179+
RETURN msg.action == MessageAction.MESSAGE_UPDATE,
180+
interval: 500ms,
181+
timeout: 10s
182+
)
159183
ASSERT updated_msg.name == "updated"
160184
ASSERT updated_msg.data == "updated-data"
161185
ASSERT updated_msg.action == MessageAction.MESSAGE_UPDATE
@@ -199,8 +223,14 @@ ASSERT delete_result IS UpdateDeleteResult
199223
ASSERT delete_result.versionSerial IS String
200224
ASSERT delete_result.versionSerial.length > 0
201225
202-
# Verify via getMessage — action should be MESSAGE_DELETE
203-
deleted_msg = AWAIT channel.getMessage(serial)
226+
# Verify via getMessage — poll until the delete is visible
227+
deleted_msg = poll_until(
228+
condition: FUNCTION() =>
229+
msg = AWAIT channel.getMessage(serial)
230+
RETURN msg.action == MessageAction.MESSAGE_DELETE,
231+
interval: 500ms,
232+
timeout: 10s
233+
)
204234
ASSERT deleted_msg.action == MessageAction.MESSAGE_DELETE
205235
```
206236

@@ -239,8 +269,14 @@ AWAIT channel.updateMessage(
239269
operation: MessageOperation(description: "second edit")
240270
)
241271
242-
# Get version history
243-
versions = AWAIT channel.getMessageVersions(serial)
272+
# Poll version history until all versions appear
273+
versions = poll_until(
274+
condition: FUNCTION() =>
275+
result = AWAIT channel.getMessageVersions(serial)
276+
RETURN result.items.length >= 3,
277+
interval: 500ms,
278+
timeout: 10s
279+
)
244280
```
245281

246282
### Assertions
@@ -328,8 +364,14 @@ AWAIT channel.annotations.publish(serial, Annotation(
328364
name: "like"
329365
))
330366
331-
# Verify annotation exists
332-
annotations = AWAIT channel.annotations.get(serial)
367+
# Verify annotation exists — poll until it appears
368+
annotations = poll_until(
369+
condition: FUNCTION() =>
370+
result = AWAIT channel.annotations.get(serial)
371+
RETURN result.items.length >= 1,
372+
interval: 500ms,
373+
timeout: 10s
374+
)
333375
ASSERT annotations.items.length >= 1
334376
335377
found = false
@@ -382,8 +424,14 @@ AWAIT channel.annotations.publish(serial, Annotation(
382424
name: "heart"
383425
))
384426
385-
# Retrieve annotations
386-
result = AWAIT channel.annotations.get(serial)
427+
# Retrieve annotations — poll until both appear
428+
result = poll_until(
429+
condition: FUNCTION() =>
430+
r = AWAIT channel.annotations.get(serial)
431+
RETURN r.items.length >= 2,
432+
interval: 500ms,
433+
timeout: 10s
434+
)
387435
```
388436

389437
### Assertions

uts/rest/integration/revoke_tokens.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ All tests use JWTs generated using a third-party JWT library, signed with
1717
the key secret using HMAC-SHA256. This avoids needing to call `requestToken()`
1818
and keeps the tests self-contained.
1919

20+
## Server Response Format
21+
22+
The Ably server returns token revocation results as a **plain JSON array** of
23+
per-target results:
24+
25+
```json
26+
[{"target": "clientId:xxx", "appliesAt": 1234567890, "issuedBefore": 1234567890}]
27+
```
28+
29+
On failure for a specific target, the element contains an `error` field instead:
30+
31+
```json
32+
[{"target": "invalidType:abc", "error": {"code": 40000, "statusCode": 400, "message": "..."}}]
33+
```
34+
35+
There is no `BatchResult` envelope — the `successCount` and `failureCount` fields
36+
(RSA17c) must be computed **client-side** by counting elements with and without an
37+
`error` field. This is consistent with how batch presence responses work (see
38+
`batch_presence.md`).
39+
2040
## Sandbox Setup
2141

2242
Tests run against the Ably Sandbox at `https://sandbox-rest.ably.io`.
@@ -55,7 +75,7 @@ the token must be rejected by the server.
5575
|------|-------------|
5676
| RSA17g | POST to `/keys/{keyName}/revokeTokens` |
5777
| RSA17b | Targets mapped to `type:value` strings |
58-
| RSA17c | Returns `BatchResult` with `successCount`, `failureCount`, `results` |
78+
| RSA17c | Returns per-target results; SDK computes `successCount`, `failureCount` client-side |
5979
| TRS2a | Success result contains `target` string |
6080
| TRS2b | Success result contains `appliesAt` timestamp |
6181
| TRS2c | Success result contains `issuedBefore` timestamp |
@@ -100,6 +120,8 @@ revoke_result = AWAIT key_client.auth.revokeTokens([
100120
])
101121
102122
# Step 3: Verify the revokeTokens response structure (RSA17c, TRS2)
123+
# Note: The server returns a plain array of per-target results.
124+
# successCount/failureCount are computed client-side (see Server Response Format).
103125
ASSERT revoke_result.successCount == 1
104126
ASSERT revoke_result.failureCount == 0
105127
ASSERT revoke_result.results.length == 1
@@ -210,6 +232,7 @@ revoke_result = AWAIT key_client.auth.revokeTokens(
210232
options: { issuedBefore: server_time, allowReauthMargin: true }
211233
)
212234
235+
# successCount is computed client-side (see Server Response Format)
213236
ASSERT revoke_result.successCount == 1
214237
ASSERT revoke_result.results.length == 1
215238
@@ -284,6 +307,7 @@ revoke_result = AWAIT key_client.auth.revokeTokens([
284307
])
285308
286309
# Step 3: Verify the response contains both success and failure
310+
# successCount/failureCount are computed client-side (see Server Response Format)
287311
ASSERT revoke_result.successCount == 1
288312
ASSERT revoke_result.failureCount == 1
289313
ASSERT revoke_result.results.length == 2

0 commit comments

Comments
 (0)