From 2f086713c6841ceb09b6a00571908eddaac71b09 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Mon, 21 Sep 2026 17:46:17 +0100 Subject: [PATCH 1/2] UTS: correct presence wire-action decoding assertions Fixes assertions in rest_presence.md that mapped wire action 4 to LEAVE; the wire ordinals are ABSENT=0, PRESENT=1, ENTER=2, LEAVE=3, UPDATE=4. Also corrects the accompanying explanatory note. Addresses #526 (Defect 1). Defect 2 (outgoing toJson encoding) is deferred pending a maintainer ruling and is not included here. Co-Authored-By: Claude Opus 4.8 --- uts/rest/unit/presence/rest_presence.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/uts/rest/unit/presence/rest_presence.md b/uts/rest/unit/presence/rest_presence.md index 979e07a50..8993a36c7 100644 --- a/uts/rest/unit/presence/rest_presence.md +++ b/uts/rest/unit/presence/rest_presence.md @@ -432,7 +432,7 @@ mock_http = MockHttpClient( captured_requests.push(req) req.respond_with(200, [ { "action": 2, "clientId": "client1", "data": "entered" }, - { "action": 4, "clientId": "client1", "data": "left" } + { "action": 3, "clientId": "client1", "data": "left" } ]) } ) @@ -1376,7 +1376,7 @@ mock_http = MockHttpClient( headers: { "Link": "; rel=\"next\"" } ) ELSE: - req.respond_with(200, body: [{ "action": 4, "clientId": "c1", "timestamp": 1000 }]) + req.respond_with(200, body: [{ "action": 3, "clientId": "c1", "timestamp": 1000 }]) } ) install_mock(mock_http) @@ -1673,11 +1673,4 @@ ASSERT result.items[3].action == PresenceAction.leave ASSERT result.items[4].action == PresenceAction.update ``` -Note: Action values may vary by SDK. The wire protocol uses: -- 0 = absent -- 1 = present -- 2 = enter -- 3 = leave (some SDKs use 4) -- 4 = update (some SDKs use 3) - -Verify against your SDK's specific mapping. +Note: The presence action is encoded on the wire as its enum ordinal, as specified in `specifications/protocol.md` (Presence Message): 0 = absent, 1 = present, 2 = enter, 3 = leave, 4 = update. From a1c8b8f7e8b6218079bd4162cc275f2d4f649fb2 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 22 Sep 2026 13:09:42 +0100 Subject: [PATCH 2/2] UTS: assert presence action as wire ordinal, not string (RSP #526 Defect 2) Under the convention that toJson()/fromJson() operate on the wire body, presence actions are the enum ordinal (protocol.md:332). Corrects the two outgoing toJson() assertions from "enter" to 2, converts nine inbound string-action fixtures to their ordinals, and records the wire-body convention in writing-test-specs.md. Co-Authored-By: Claude Opus 4.8 --- uts/docs/writing-test-specs.md | 2 ++ uts/rest/unit/types/presence_message_types.md | 22 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/uts/docs/writing-test-specs.md b/uts/docs/writing-test-specs.md index aa403e6b2..393c8cf94 100644 --- a/uts/docs/writing-test-specs.md +++ b/uts/docs/writing-test-specs.md @@ -591,6 +591,8 @@ ASSERT request.url.path CONTAINS "/channels/" Use `toJson()` and `fromJson()` as the portable pseudocode names for serializing to and deserializing from wire format. These are language-agnostic — implementations will map them to the appropriate mechanism (e.g., `toMap()`/`fromMap()` in Dart, `toJSON()`/`fromJSON()` in JavaScript, `to_dict()`/`from_dict()` in Python). +A `toJson()` assertion is made against the wire body, and `fromJson()` consumes the wire body. Assert fields using their wire encoding, not a language model's representation — for example, presence and message actions are asserted as their enum ordinal (e.g. `2` for `ENTER`), not the string name. + ```pseudo # Serializing to wire format json_data = message.toJson() diff --git a/uts/rest/unit/types/presence_message_types.md b/uts/rest/unit/types/presence_message_types.md index 47596367b..1acf63bad 100644 --- a/uts/rest/unit/types/presence_message_types.md +++ b/uts/rest/unit/types/presence_message_types.md @@ -119,7 +119,7 @@ protocol_msg = ProtocolMessage( action: PRESENCE, connectionId: "proto-conn-1", presence: [ - { "action": "enter", "clientId": "user-1" } + { "action": 2, "clientId": "user-1" } ] ) @@ -143,8 +143,8 @@ protocol_msg = ProtocolMessage( action: PRESENCE, id: "proto-msg-42", presence: [ - { "action": "enter", "clientId": "alice" }, - { "action": "enter", "clientId": "bob" } + { "action": 2, "clientId": "alice" }, + { "action": 2, "clientId": "bob" } ] ) @@ -168,7 +168,7 @@ protocol_msg = ProtocolMessage( action: PRESENCE, timestamp: 9999999, presence: [ - { "action": "enter", "clientId": "user-1" } + { "action": 2, "clientId": "user-1" } ] ) @@ -188,7 +188,7 @@ ASSERT presence_msg.timestamp == 9999999 ```pseudo json_data = { "id": "pm-123", - "action": "enter", + "action": 2, "clientId": "user-1", "connectionId": "conn-1", "data": "hello", @@ -228,7 +228,7 @@ ASSERT msg.extras["headers"]["x-key"] == "x-value" ```pseudo FOR EACH test_case IN test_cases: json_data = { - "action": "enter", + "action": 2, "clientId": "user-1", "data": test_case.wire_data, "encoding": test_case.encoding @@ -259,7 +259,7 @@ msg = PresenceMessage( json_data = msg.toJson() -ASSERT json_data["action"] == "enter" +ASSERT json_data["action"] == 2 ASSERT json_data["clientId"] == "user-1" ASSERT json_data["data"] == "hello" ASSERT json_data["extras"]["headers"]["x-key"] == "x-value" @@ -280,7 +280,7 @@ msg = PresenceMessage(action: ENTER, clientId: "user-1") json_data = msg.toJson() -ASSERT json_data["action"] == "enter" +ASSERT json_data["action"] == 2 ASSERT json_data["clientId"] == "user-1" ASSERT "data" NOT IN json_data OR json_data["data"] IS null ASSERT "encoding" NOT IN json_data OR json_data["encoding"] IS null @@ -302,7 +302,7 @@ decoded and decrypted PresenceMessage(s). Behavior is the same as TM3. ```pseudo # fromEncoded — single message raw = { - "action": "enter", + "action": 2, "clientId": "user-1", "data": "{\"status\":\"online\"}", "encoding": "json" @@ -317,8 +317,8 @@ ASSERT msg.encoding IS null # fromEncodedArray — array of messages raw_array = [ - { "action": "enter", "clientId": "alice", "data": "hello" }, - { "action": "enter", "clientId": "bob", "data": "world" } + { "action": 2, "clientId": "alice", "data": "hello" }, + { "action": 2, "clientId": "bob", "data": "world" } ] messages = PresenceMessage.fromEncodedArray(raw_array)