Skip to content

Add UTS test specs for the LiveObjects REST API (RSO*) - #569

Open
ttypic wants to merge 2 commits into
mainfrom
AIT-468/liveobjects-rest-spec-uts
Open

ttypic wants to merge 2 commits into
mainfrom
AIT-468/liveobjects-rest-spec-uts

Conversation

@ttypic

@ttypic ttypic commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Based on #476

Adds UTS test specs for the LiveObjects REST API (RSL16, RSO1–RSO16) specified in the base PR (AIT-468/liveobjects-rest-spec). Assertions are based on that spec, with the behaviour list in the earlier REST-OBJECTS-PROPOSAL.md (branch uts-liveobjects-rest) used as a checklist for the integration tests.

What's added

File Tests Covers
uts/objects/unit/rest_object_get.md 16 RestChannel#object (one instance per channel; error 40019 without the plugin), get endpoint and percent-encoded objectId segment, path/compact query params, paths passed through verbatim (RSO6), compact results returned verbatim (JSON and msgpack), full-mode decoding into RestLiveMap / RestLiveCounter / ObjectData leaves (including empty maps and empty or falsy leaves), unrecognised shapes passed through, server errors surfaced
uts/objects/unit/rest_object_publish.md 17 POST body as an array (JSON and msgpack), batches in a single request, no client-side batch limit, wire shape of all 7 operation types with no action field, id/objectId/path preserved verbatim, semantics and leaf-value encoding per OMP2/OD4, no auto-generated id even with idempotentRestPublishing, 40003 for zero or multiple body fields, targeting not validated client-side, RestObjectPublishResult decoding
uts/objects/unit/rest_object_generate_id.md 12 Map/counter type prefix, objectId derived per RTO14 from initialValue, nonce and server time, initialValue contents, JSON encoding even on a msgpack client, unrecognised semantics / missing body → 40003, nonce of 16+ characters, unique per call, deterministic initialValue, generated values sent unchanged in *WithObjectId operations
uts/objects/integration/rest_object_test.md 14 Sandbox round-trips for JSON and msgpack: all leaf types, edge-case leaf values (empty string/bytes, 0, false, ±(2^53 − 1)), counter create/increment, standalone counterCreate, standalone and path-targeted mapCreate, mapRemove, wildcard path with mapSet, mapRemove and counterInc, realtime-written state read via REST, client-generated IDs accepted in one batch, idempotent id, error surfacing for unresolvable targets

uts/docs/completion-status.md gains an RSL16 row and a RestObject (RSO) section.

Test IDs use the objects/unit/... and objects/integration/... categories, matching the LiveObjects tests already on main. This branch was cut before uts/objects/ landed on main, but the new file names don't clash with it.

Points for reviewers

These tests depend on spec wording that is ambiguous or may not match the service:

  1. JSON leaf encoding. RSO3d2 / RSO4e1b point to OD4d5, so the tests expect JSON values on the wire as string + encoding: "json". main adds an OD2g json field, which suggests the real wire format may differ.
  2. semantics on the wire. Requests and mocked full-mode responses use the OMP2 integer (0 = LWW). If the server actually returns "lww", the mocked responses need updating.
  3. generateObjectId and server time. RSO4b says the function does no I/O, but RSO4g requires server time per RTO16, which means a GET /time on a fresh client. The unit tests follow RSO4g: they mock /time and assert the exact objectId timestamp. If local time is intended, RSO4g should change and so should these tests.
  4. objectId path encoding. Tests use encode_uri_component, which escapes : and @. RFC 3986 allows both unescaped in a path segment.
  5. Compact formats for bytes and JSON. The specs leave compact-mode bytes and JSON values server-defined (RSO8) and don't assert them. ably-js tests that the sandbox returns bytes as base64 (JSON protocol) or binary (msgpack), and JSON objects/arrays as JSON strings. Should RSO8 specify this?
  6. Integration server assumptions. The wildcard (items.*) and idempotent-id tests, and the compact-map assertions, assume server behaviour that hasn't been checked against the sandbox yet.

@ttypic
ttypic requested a review from VeskeR October 30, 2026 08:40
@ttypic
ttypic force-pushed the AIT-468/liveobjects-rest-spec-uts branch from af03d95 to 51095e7 Compare November 4, 2026 11:45
@ttypic
ttypic marked this pull request as ready for review November 4, 2026 13:52
ttypic added a commit to ably/ably-pubsub-java that referenced this pull request Nov 4, 2026
Implement RestObject (RSL16, RSO1-RSO16 in ably/specification#569) as
HttpObject, following ably-js's Rest -> Http naming. It is built into
the HTTP client in lib, needs no plugin, and follows the HTTP channel's
async convention: blocking methods plus ...Async(..., Callback) variants.

- get: compact results are returned as plain Java values; full results
  decode into HttpLiveMap, HttpLiveCounter, HttpObjectData leaves, or a
  plain HttpLiveObject for unrecognised types.
- publish: operations are built with factories, so each has exactly one
  body. No operation ids are generated (RSO3f), so a publish goes to the
  current host only and is never retried on a fallback host, which could
  apply a batch twice.
- generateObjectId: derives the ID locally (RTO14) from per-client server
  time; concurrent first callers share one /time request.

JSON leaf values go on the wire in the OD2g `json` field rather than the
OD4d5 `string` + `encoding: "json"` form: the sandbox accepts the latter
but drops `encoding` on storage, so JSON would read back as a string.

Translate the UTS specs with the uts-to-kotlin skill into :core
(uts/unit/objects, uts/integration/standard/objects). The realtime-write
REST-read interop test stays in :liveobjects, as it needs the realtime
plugin. HttpObjectEdgeCasesTest covers async callbacks, error handling,
the no-fallback publish and the shared server-time request. Deviations
are recorded in deviations.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttypic added a commit to ably/ably-pubsub-java that referenced this pull request Nov 4, 2026
Implement RestObject (RSL16, RSO1-RSO16 in ably/specification#569) as
HttpObject, following ably-js's Rest -> Http naming. It is built into
the HTTP client in lib, needs no plugin, and follows the HTTP channel's
async convention: blocking methods plus ...Async(..., Callback) variants.

- get: compact results are returned as plain Java values; full results
  decode into HttpLiveMap, HttpLiveCounter, HttpObjectData leaves, or a
  plain HttpLiveObject for unrecognised types.
- publish: operations are built with factories, so each has exactly one
  body. No operation ids are generated (RSO3f), so a publish goes to the
  current host only and is never retried on a fallback host, which could
  apply a batch twice.
- generateObjectId: derives the ID locally (RTO14) from per-client server
  time; concurrent first callers share one /time request.

JSON leaf values go on the wire in the OD2g `json` field rather than the
OD4d5 `string` + `encoding: "json"` form: the sandbox accepts the latter
but drops `encoding` on storage, so JSON would read back as a string.

Translate the UTS specs with the uts-to-kotlin skill into :core
(uts/unit/objects, uts/integration/standard/objects). The realtime-write
REST-read interop test stays in :liveobjects, as it needs the realtime
plugin. HttpObjectEdgeCasesTest covers async callbacks, error handling,
the no-fallback publish and the shared server-time request. Deviations
are recorded in deviations.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The REST counterpart of LiveObjects shipped in ably-js in [1].
This spec PR adds an RSO* section under objects-features so
other SDKs have a contract to implement against.

Decisions worth recording:

- Same Objects plugin, both transports. Rather than introducing a
  separate PluginType for REST, PC5 now covers both RealtimeObjects
  and RestObject. This matches the ably-js packaging and keeps a
  single plugin concept for users.

- RestObjectOperation as a flat discriminated union. Common targeting
  fields plus exactly-one body field, mirroring the wire format and
  the existing "set exactly one field" patterns (OD2, OOP3,
  PublishObjectData). A class hierarchy was considered but rejected:
  consistency with the rest of the Objects spec and the 1:1 mapping
  to the wire format won, and languages without sum types (Go,
  Python, PHP) gain little from inheritance either.

- Paths are opaque to the SDK. RSO6 explicitly forbids client-side
  parsing or normalization. Path syntax (wildcards, escaping) is
  server-defined and can evolve without coupling SDK releases to it.

- Full-mode leaves reuse ObjectData. Rather than introducing a
  parallel RestObjectData type, RSO9c defers to OD5 with explicit
  guarantees about which fields are unset post-decode. Avoids type
  duplication.

- Client-side object IDs via generateObjectId. Realtime always
  generates IDs locally (RTO14); REST normally delegates to the
  server but supports local generation for batches where one
  operation needs to reference another newly-created object by ID.
  RSO5j documents this asymmetry so implementers understand why both
  Create and CreateWithObjectId variants exist.

[1] ably/ably-pubsub-js#2109
@ttypic
ttypic force-pushed the AIT-468/liveobjects-rest-spec-uts branch from 51095e7 to 2681e40 Compare November 4, 2026 18:21
@ttypic
ttypic force-pushed the AIT-468/liveobjects-rest-spec branch from e066285 to 6f1518c Compare November 4, 2026 18:22
ttypic added a commit to ably/ably-pubsub-java that referenced this pull request Nov 4, 2026
Implement RestObject (RSL16, RSO1-RSO16 in ably/specification#569) as
HttpObject, following ably-js's Rest -> Http naming. It is built into
the HTTP client in lib, needs no plugin, and follows the HTTP channel's
async convention: blocking methods plus ...Async(..., Callback) variants.

- get: compact results are returned as plain Java values; full results
  decode into HttpLiveMap, HttpLiveCounter, HttpObjectData leaves, or a
  plain HttpLiveObject for unrecognised types.
- publish: operations are built with factories, so each has exactly one
  body. No operation ids are generated (RSO3f), so a publish goes to the
  current host only and is never retried on a fallback host, which could
  apply a batch twice.
- generateObjectId: derives the ID locally (RTO14) from per-client server
  time; concurrent first callers share one /time request.

JSON leaf values go on the wire in the OD2g `json` field rather than the
OD4d5 `string` + `encoding: "json"` form: the sandbox accepts the latter
but drops `encoding` on storage, so JSON would read back as a string.

Translate the UTS specs with the uts-to-kotlin skill into :core
(uts/unit/objects, uts/integration/standard/objects). The realtime-write
REST-read interop test stays in :liveobjects, as it needs the realtime
plugin. HttpObjectEdgeCasesTest covers async callbacks, error handling,
the no-fallback publish and the shared server-time request. Deviations
are recorded in deviations.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttypic added a commit to ably/ably-pubsub-java that referenced this pull request Nov 5, 2026
Implement RestObject (RSL16, RSO1-RSO16 in ably/specification#569) as
HttpObject, following ably-js's Rest -> Http naming. It is built into
the HTTP client in lib, needs no plugin, and follows the HTTP channel's
async convention: blocking methods plus ...Async(..., Callback) variants.

- get: compact results are returned as plain Java values; full results
  decode into HttpLiveMap, HttpLiveCounter, HttpObjectData leaves, or a
  plain HttpLiveObject for unrecognised types.
- publish: operations are built with factories, so each has exactly one
  body. No operation ids are generated (RSO3f), so a publish goes to the
  current host only and is never retried on a fallback host, which could
  apply a batch twice.
- generateObjectId: derives the ID locally (RTO14) from per-client server
  time; concurrent first callers share one /time request.

JSON leaf values go on the wire in the OD2g `json` field rather than the
OD4d5 `string` + `encoding: "json"` form: the sandbox accepts the latter
but drops `encoding` on storage, so JSON would read back as a string.

Translate the UTS specs with the uts-to-kotlin skill into :core
(uts/unit/objects, uts/integration/standard/objects). The realtime-write
REST-read interop test stays in :liveobjects, as it needs the realtime
plugin. HttpObjectEdgeCasesTest covers async callbacks, error handling,
the no-fallback publish and the shared server-time request. Deviations
are recorded in deviations.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttypic added a commit to ably/ably-pubsub-java that referenced this pull request Nov 5, 2026
Implement RestObject (RSL16, RSO1-RSO16 in ably/specification#569) as
HttpObject, following ably-js's Rest -> Http naming. It is built into
the HTTP client in lib, needs no plugin, and follows the HTTP channel's
async convention: blocking methods plus ...Async(..., Callback) variants.

- get: compact results are returned as plain Java values; full results
  decode into HttpLiveMap, HttpLiveCounter, HttpObjectData leaves, or a
  plain HttpLiveObject for unrecognised types.
- publish: operations are built with factories, so each has exactly one
  body. No operation ids are generated (RSO3f), so a publish goes to the
  current host only and is never retried on a fallback host, which could
  apply a batch twice.
- generateObjectId: derives the ID locally (RTO14) from per-client server
  time; concurrent first callers share one /time request.

JSON leaf values go on the wire in the OD2g `json` field rather than the
OD4d5 `string` + `encoding: "json"` form: the sandbox accepts the latter
but drops `encoding` on storage, so JSON would read back as a string.

Translate the UTS specs with the uts-to-kotlin skill into :core
(uts/unit/objects, uts/integration/standard/objects). The realtime-write
REST-read interop test stays in :liveobjects, as it needs the realtime
plugin. HttpObjectEdgeCasesTest covers async callbacks, error handling,
the no-fallback publish and the shared server-time request. Deviations
are recorded in deviations.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@VeskeR VeskeR left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to your "Points for reviewers" in the PR descrpition:

  1. JSON leaf encoding - I think it's fixed in yours 2681e40 ? the only suggestion is to move to its own separate PR
  2. semantics - "full" get responses from the server return LWW (uppercase). In realtime on wire we use integers - 0 for this. I think this is something for me to fix in #476 or in a follow-up, I wouldn't block this PR on this. I think you correctly mocked returning uppercase LWW for full get responses.
  3. generateObjectId does need server time, need to clarify #476 or in a follow up
  4. not sure about the impact/where (if) this is an issue
  5. specify what exactly? I think generally specifying server's formatting is outside the spec and we don't do that?
  6. not sure what this means. I believe the non-UTS tests in ably-js had tests for these?

I think due to some potential changes to #476 I described above some UTS tests will be slighlty altered here. I'm happy to address 1-3 in a follow-up PR and fix UTS spec in tandem

Comment thread specifications/features.md Resolved
Comment thread specifications/features.md Resolved
Unit specs (mocked HTTP) for RestChannel#object, RestObject#get,
RestObject#publish and RestObject#generateObjectId, plus a sandbox
integration spec covering end-to-end round-trips in both JSON and
msgpack protocol variants. Update the completion-status matrix with
RSL16 and the RSO section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@VeskeR VeskeR left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.
Agreed in private re #569 (review) - points 1-3 we will address in the follow-up PR to clear out spec inconsistencies, points 4-6 we most likely won't do

@ttypic
ttypic enabled auto-merge (rebase) November 13, 2026 16:47

This branch was successfully deployed

1 active deployment
staging/pull/569 — 0d761505 Deployed Nov 12, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants