Skip to content

[Split 4/4] Run the UTS through the server door's builders - #1235

Merged
ttypic merged 5 commits into
integration/v2from
integration/split-4-uts-side-modes
Sep 17, 2026
Merged

ttypic merged 5 commits into
integration/v2from
integration/split-4-uts-side-modes

Conversation

@umair-ably

@umair-ably umair-ably commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fourth PR in the PDR-091b split stack (stacked on #1234; diff shows only this PR's changes). Mirrors ably/ably-js#2294.

What this PR does

  • uts.side mode on the UTS's single construction seam (ClientFactories.kt, used by all 10 unit + integration test files): core (default) keeps the core constructors; server routes both client kinds through PubSubServer.httpClientBuilder/realtimeClientBuilder. The builders only stamp the versionless side flag and pass everything else through, so every UTS behaviour must pass identically through the door — that's the conformance claim this PR adds.
  • CI runs both legs: check.yml (UTS unit) and integration-test.yml (check-uts) each gain a -Duts.side=server invocation.
  • Harness self-test (SideModesTest, the analogue of ably-js's side_modes.test.ts): captures the actual Ably-Agent header via the mock HTTP engine and asserts the selected mode's stamp — bare ably-pubsub-server token in server mode (fails on any /version regression, per ably-common#361), no side token in core mode, ably-java/ always present. Exists so a broken seam can't silently turn the server leg into a duplicate core run. @Timeout(30) so a seam regression fails rather than hangs.
  • Completes DebugOptions.copy() with the four fields base ClientOptions.copy() was also missing (fixed in [Split 2/3] Add io.ably.pubsub:device and :server door packages with side-declaring agents #1233): headers, fallbackHosts, transportParams, agents. This matters here because the door stamping relies on the polymorphic copy() to carry the suite's mock hooks (httpEngine, webSocketEngineFactory, clock, listeners) through construction — the full server-mode suite passing green is the proof.

Where this differs from ably-js (deliberately)

  • No device leg: io.ably.pubsub:device is an Android AAR and cannot run on the JVM the UTS uses (ably-js's device package runs fine in Node). The device door's stamping contract is covered by the instrumentation tests in the device module (emulate.yml). Recorded in the uts README.
  • No constructor-shaped wrapper trickery needed: Kotlin's factory-function seam (TestRealtimeClient {}) already is the ably-js sideConstructor in spirit, so the change is a when on the mode.
  • The two auxiliary AblyRest(...) constructions (liveobjects REST provisioning, proxy token signer) stay on core constructors — they're test infrastructure, not the SDK under test.

Verified locally: full :uts:runUtsUnitTests green in both modes; SideModesTest runs and asserts in both.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Copying debug configuration now preserves headers, fallback hosts, transport parameters, and agent settings.
  • Testing

    • Expanded automated coverage for core and server client modes, including authentication, token requests, proxy reauthentication, and agent identification.
    • Added validation for server-side client builders and consistent behavior across REST and realtime clients.
  • Documentation

    • Documented test configuration options for selecting client modes and authentication behavior.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: f863be08-bbde-4485-bafe-adf616db7efb

📥 Commits

Reviewing files that changed from the base of the PR and between bf00ff4 and fb91b48.

📒 Files selected for processing (10)
  • .github/workflows/check.yml
  • .github/workflows/integration-test.yml
  • lib/src/main/java/io/ably/lib/debug/DebugOptions.java
  • uts/README.md
  • uts/build.gradle.kts
  • uts/src/test/kotlin/io/ably/lib/uts/infra/integration/AblyJwt.kt
  • uts/src/test/kotlin/io/ably/lib/uts/infra/unit/ClientFactories.kt
  • uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/realtime/AuthReauthTest.kt
  • uts/src/test/kotlin/io/ably/lib/uts/integration/standard/realtime/TokenRequestTest.kt
  • uts/src/test/kotlin/io/ably/lib/uts/unit/SideModesTest.kt

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

The UTS harness now selects core or server client builders. Tests validate side-specific agent stamps and authentication behavior. CI runs both modes. DebugOptions.copy() now copies four previously omitted fields.

Changes

UTS side-mode coverage

Layer / File(s) Summary
Side selection and agent validation
uts/build.gradle.kts, uts/src/test/kotlin/io/ably/lib/uts/infra/unit/ClientFactories.kt, uts/src/test/kotlin/io/ably/lib/uts/unit/SideModesTest.kt, lib/src/main/java/io/ably/lib/debug/DebugOptions.java
The UTS build propagates uts.side. Client factories select core or server builders. SideModesTest validates agent stamps. DebugOptions.copy() copies headers, fallback hosts, transport parameters, and agents.
Authentication coverage for server mode
uts/src/test/kotlin/io/ably/lib/uts/infra/integration/AblyJwt.kt, uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/realtime/AuthReauthTest.kt, uts/src/test/kotlin/io/ably/lib/uts/integration/standard/realtime/TokenRequestTest.kt
The tests add HS256 JWT creation with optional client claims. Reauthentication uses JWTs on the server leg. Token-consuming clients use core construction for native-token cases.
CI and usage documentation
.github/workflows/check.yml, .github/workflows/integration-test.yml, uts/README.md
CI runs unit and integration tests with uts.side=server. The UTS documentation describes side selection, agent stamping, and token-auth handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Suggested reviewers: sacoo7, ttypic

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch integration/split-4-uts-side-modes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit reads each line,
The patch grows clear beneath the moon,
Small changes hop in place,
Tests guard the garden path,
Reviews bloom before the dawn.

Comment @coderabbitai help to get the list of available commands.

@umair-ably

Copy link
Copy Markdown
Contributor Author

The failed check-uts leg turned out to be a real finding, not a test bug — arguably the strongest justification for this PR existing:

40167: a connection or request may only declare itself as a server via a signed x-ably-clientType token claim

Sandbox realtime already enforces that on token auth the agent entry alone may not claim the server side — the side must come from a signed x-ably-clientType token claim, which is PDR-091 deferred decision D2 and isn't implemented anywhere yet. Key-auth server clients connect fine (verified against sandbox with the bare flag); exactly the token-auth tests (TokenRequestTest ×2, AuthReauthTest) failed. ably-js's UTS never hit this because it's fully mocked — this repo's sandbox-backed UTS leg is what caught it.

Resolution here: token-auth tests call assumeSideSupportsTokenAuth() and are skipped (not failed) on the server leg with the 40167/D2 rationale in the message; the core leg still runs them. When D2 lands, the test infra can mint the claim and the assumption gets deleted (tracked in the uts README).

Programme-level implication worth escalating (cc D2's owner): as enforced today, any customer using the server package with token auth gets 40167 — if prod behaves like sandbox, the server door is effectively key-auth-only until D2 ships. That sequencing constraint (D2 before/with the pubsub GA) doesn't appear in the rollout plan.

@umair-ably

Copy link
Copy Markdown
Contributor Author

No more skips on the server leg. The token-auth handling is now per token format:

  • JWTs (AuthReauthTest): the test mints its own JWT (AblyJwt, HS256 via JDK crypto) and adds x-ably-clientType=server on the server leg — the declaration realtime requires on token auth. Verified against sandbox; also the first end-to-end proof the claim path works through the new server package.
  • Native tokens (TokenRequestTest): the format can't carry the claim, but the failing part was only a client authenticating itself with a native token while flagged as a server. That's not how the feature is used — servers mint native tokens for devices — so the test now splits its clients across the seam: the minting client (the createTokenRequest surface under test) goes through the server door on every leg, and the consuming client models the device and stays a plain core client. Full RSA9 coverage through the door, no skip, and the structure now mirrors real usage.

assumeSideSupportsTokenAuth() is deleted — nothing needs it. If the platform later adds the claim to native tokens, a "server self-auth via native token" test can be added then; if it never does, the suite already reflects that combination as unsupported.

umair-ably and others added 4 commits September 17, 2026 08:51
Mirrors ably-js#2294: the UTS constructs every client through a single
seam (TestRealtimeClient/TestRestClient in ClientFactories.kt), now
selected by the uts.side system property — `core` (default) keeps the
core constructors; `server` routes both client kinds through
PubSubServer's side-stamping builders. The builders only stamp the
side-declaring agent flag and pass everything else through (DebugOptions
included, via its copy() override), so conformance must be identical on
both legs; CI runs both for the UTS unit and integration tiers.

A harness self-test (SideModesTest, mirroring ably-js's
side_modes.test.ts) asserts each mode's stamp on the wire via the mock
HTTP engine — bare versionless flag in server mode, none in core mode —
so a broken seam cannot silently degrade the server leg into a duplicate
core run.

Unlike ably-js there is no device leg: io.ably.pubsub:device is an
Android artifact and cannot run on the JVM this suite uses; its stamping
contract is covered by the device module's instrumentation tests.

Also completes DebugOptions.copy() with the same four fields the base
ClientOptions.copy() was missing (headers, fallbackHosts,
transportParams, agents) — the door stamping relies on the polymorphic
copy() to carry the suite's mock hooks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The server-mode UTS leg surfaced a real platform behavior: realtime
rejects a token-authenticated connection that declares the server side
through the agent entry alone, with 40167 "a connection or request may
only declare itself as a server via a signed x-ably-clientType token
claim". The signed-claim mechanism is PDR-091 deferred decision D2 and
does not exist yet, so nothing the test infrastructure can mint will
authenticate a token-auth server client.

Key-auth server clients are unaffected (verified against sandbox), so
the two token-auth test classes (TokenRequestTest, AuthReauthTest) now
call assumeSideSupportsTokenAuth() and are reported skipped, not failed,
on the server leg; the core leg still runs them. When D2 lands, the test
infra can mint the claim and the assumption gets deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UTS spec authenticates this test with a JWT; the Java port had
substituted a native TokenRequest for convenience. Restoring the JWT
(AblyJwt: HS256 via JDK crypto, no external library) lets the test carry
the signed x-ably-clientType=server claim on the server leg — the only
server-side declaration realtime accepts on token auth — so it now runs
on every leg instead of being skipped. Verified against sandbox: the
claim-bearing JWT connects and re-authenticates where the bare agent
flag was rejected with 40167.

assumeSideSupportsTokenAuth stays for the native-token tests
(TokenRequestTest): the native token format cannot carry the claim yet.

Also drops DR/ticket numbers from code comments in this PR's files;
the behavior is described in place instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntifier

The family identifier shares the ably-pubsub- prefix with the side
flags, so the harness self-test now matches the side identifiers exactly
(via the Side constants) rather than by prefix, and asserts the renamed
family entry is always present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TokenRequestTest's two clients now sit on opposite sides of the seam,
matching how native tokens are really used: the minting client — the
createTokenRequest surface under test — goes through the door on every
leg, so the server leg exercises token minting through the server
package; the consuming client models the device the token was minted
for and is always a plain core client, since a client may not
authenticate itself with a native token while declaring the server side
(realtime rejects that with 40167, and the native token format cannot
carry the required signed claim).

Nothing is skipped on any leg any more, so assumeSideSupportsTokenAuth
is deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ttypic
ttypic force-pushed the integration/split-4-uts-side-modes branch from dfc2b19 to fb91b48 Compare September 17, 2026 07:51
@ttypic
ttypic marked this pull request as ready for review September 17, 2026 07:52
@ttypic
ttypic merged commit 77a6677 into integration/v2 Sep 17, 2026
7 of 16 checks passed
@ttypic
ttypic deleted the integration/split-4-uts-side-modes branch September 17, 2026 07:52
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