Skip to content

Commit b99ea4c

Browse files
umair-ablyclaude
authored andcommitted
Run native-token conformance on every UTS leg by splitting the clients
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>
1 parent 4d9ee99 commit b99ea4c

4 files changed

Lines changed: 34 additions & 33 deletions

File tree

‎uts/README.md‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -813,12 +813,20 @@ Unlike ably-js's UTS there is no `device` mode: `io.ably.pubsub:device` is an An
813813
so its door cannot run on the JVM this suite uses. Its stamping contract is covered by the
814814
instrumentation tests in the `device` module (`emulate.yml`).
815815

816-
**Token-auth tests are skipped on the server leg.** Realtime rejects a token-authenticated
817-
connection that declares the server side through the agent entry alone (error 40167: the side
818-
must come from a signed `x-ably-clientType` token claim — PDR-091 deferred decision D2, not yet
819-
implemented anywhere the test infra can reach). Tests whose clients use token auth call
820-
`assumeSideSupportsTokenAuth()` and are reported as skipped, not failed, in server mode. When D2
821-
lands, mint the claim in the test infra and delete the assumption.
816+
**Token auth on the server leg.** Realtime rejects a token-authenticated connection that
817+
declares the server side through the agent entry alone (error 40167: on token auth the side
818+
must come from a signed `x-ably-clientType` token claim). The suite handles this per token
819+
format, with nothing skipped:
820+
821+
- **JWTs** can carry the claim already: tests that authenticate the client under test with a
822+
token mint one via `AblyJwt` (HS256, JDK crypto), adding `x-ably-clientType=server` on the
823+
server leg (see `AuthReauthTest`).
824+
- **Native tokens** cannot carry the claim yet, so a client may not authenticate *itself* with
825+
one while declaring the server side. `TokenRequestTest` therefore splits its clients across
826+
the seam, matching how the feature is really used: the **minting** client (the
827+
`createTokenRequest` surface under test) goes through the door on every leg, and the
828+
**consuming** client — modelling the device the token was minted for — is always a plain
829+
core client.
822830

823831
Notes:
824832
- `ProxyManager` **advises** running proxy suites single-fork (`maxParallelForks = 1`) because they

‎uts/src/test/kotlin/io/ably/lib/uts/infra/integration/AblyJwt.kt‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import javax.crypto.spec.SecretKeySpec
1111
* notably `x-ably-clientType=server`: on token auth the realtime service accepts a
1212
* server-side declaration only from that signed claim (a `-server` agent entry alone is
1313
* rejected with error 40167), and the native token format cannot carry the claim yet. So a
14-
* JWT is the one way a token-authenticated client can declare the server side, and JWT-based
15-
* tests can run on the server UTS leg while native-token tests remain skipped (see
16-
* assumeSideSupportsTokenAuth).
14+
* JWT is the one way a token-authenticated client can declare the server side, which lets
15+
* JWT-based tests run on the server UTS leg. (Native-token tests instead keep their
16+
* token-consuming client on the core constructors, modelling the device the token was minted
17+
* for — see TokenRequestTest.)
1718
*/
1819
object AblyJwt {
1920
/**

‎uts/src/test/kotlin/io/ably/lib/uts/infra/unit/ClientFactories.kt‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,6 @@ class ClientOptionsBuilder : DebugOptions("appId.keyId:keySecret") {
3737
*/
3838
val utsSide: String = System.getProperty("uts.side").let { if (it.isNullOrEmpty()) "core" else it }
3939

40-
/**
41-
* Call at the top of any test whose client authenticates with a native Ably token.
42-
*
43-
* Realtime rejects a token-authenticated connection that declares the server side through the
44-
* agent entry alone: error 40167, "a connection or request may only declare itself as a server
45-
* via a signed x-ably-clientType token claim". The native Ably token format cannot carry that
46-
* claim yet, so until the platform supports it, native-token conformance runs on the core leg
47-
* only and is skipped (not failed) on the server leg. Key-auth tests are unaffected — on
48-
* API-key auth the agent entry is the accepted declaration — and JWT-based tests can carry the
49-
* claim already (see AblyJwt), so they run on every leg instead of calling this.
50-
*/
51-
fun assumeSideSupportsTokenAuth() = org.junit.jupiter.api.Assumptions.assumeTrue(
52-
utsSide == "core",
53-
"native-token conformance is skipped on the '$utsSide' leg: realtime requires a signed " +
54-
"x-ably-clientType token claim (40167) to declare the server side on token auth, " +
55-
"and the native token format cannot carry that claim yet",
56-
)
57-
5840
fun TestRealtimeClient(block: ClientOptionsBuilder.() -> Unit): AblyRealtime {
5941
val options = ClientOptionsBuilder().apply(block)
6042
return when (utsSide) {

‎uts/src/test/kotlin/io/ably/lib/uts/integration/standard/realtime/TokenRequestTest.kt‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package io.ably.lib.uts.integration.standard.realtime
22

3+
import io.ably.lib.realtime.AblyRealtime
34
import io.ably.lib.realtime.ConnectionState
45
import io.ably.lib.rest.Auth
56
import io.ably.lib.uts.infra.awaitState
67
import io.ably.lib.uts.infra.integration.SandboxApp
7-
import io.ably.lib.uts.infra.unit.assumeSideSupportsTokenAuth
8-
import io.ably.lib.uts.infra.unit.TestRealtimeClient
8+
import io.ably.lib.uts.infra.unit.ClientOptionsBuilder
99
import io.ably.lib.uts.infra.unit.TestRestClient
1010
import kotlinx.coroutines.runBlocking
1111
import kotlinx.coroutines.test.runTest
@@ -30,6 +30,14 @@ import kotlin.time.Duration.Companion.seconds
3030
* server. A REST client signs the TokenRequest; a separate realtime client exchanges it (through
3131
* its `authCallback`) for a token and connects, proving the server accepted it.
3232
*
33+
* The two clients deliberately sit on different sides of the seam. The **minting** client — the
34+
* RSA9 surface under test — goes through [TestRestClient], so on the server UTS leg it exercises
35+
* `createTokenRequest` through the server door, the shape a real server has: mint native tokens
36+
* for others. The **consuming** client models the device those tokens are minted for, so it is
37+
* always a plain core client: a client may not authenticate *itself* with a native token while
38+
* declaring the server side (realtime rejects that with 40167 — on token auth the side must come
39+
* from the signed x-ably-clientType claim, which the native token format cannot carry).
40+
*
3341
* Spec points: RSA9, RSA9a, RSA9g. Source spec: `realtime/integration/auth/token_request_test.md`.
3442
*/
3543
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -47,21 +55,24 @@ class TokenRequestTest {
4755
if (::app.isInitialized) app.delete()
4856
}
4957

58+
/** The token-consuming client — a plain core client on every leg; see the class doc. */
59+
private fun tokenConsumingClient(block: ClientOptionsBuilder.() -> Unit): AblyRealtime =
60+
AblyRealtime(ClientOptionsBuilder().apply(block))
61+
5062
/**
5163
* @UTS realtime/integration/RSA9a/token-request-server-accepted-0
5264
* @UTS realtime/integration/RSA9g/token-request-server-accepted-0
5365
*/
5466
@Test
5567
fun `RSA9a, RSA9g - createTokenRequest produces server-accepted token`() = runTest {
56-
assumeSideSupportsTokenAuth()
5768
// Client A signs TokenRequests locally with the API key (no network).
5869
val creator = TestRestClient {
5970
key = app.defaultKey
6071
restHost = SandboxApp.sandboxHost
6172
}
6273

6374
// Client B connects using a TokenRequest produced by client A.
64-
val client = TestRealtimeClient {
75+
val client = tokenConsumingClient {
6576
authCallback = Auth.TokenCallback { params -> creator.auth.createTokenRequest(params, null) }
6677
realtimeHost = SandboxApp.sandboxHost
6778
restHost = SandboxApp.sandboxHost
@@ -87,7 +98,6 @@ class TokenRequestTest {
8798
*/
8899
@Test
89100
fun `RSA9 - createTokenRequest with clientId`() = runTest {
90-
assumeSideSupportsTokenAuth()
91101
val testClientId = "token-request-client-" + UUID.randomUUID()
92102

93103
val creator = TestRestClient {
@@ -97,7 +107,7 @@ class TokenRequestTest {
97107

98108
// The TokenRequest is signed with the specific clientId, producing a token that
99109
// authenticates the client with that identity.
100-
val client = TestRealtimeClient {
110+
val client = tokenConsumingClient {
101111
authCallback = Auth.TokenCallback { params ->
102112
params.clientId = testClientId
103113
creator.auth.createTokenRequest(params, null)

0 commit comments

Comments
 (0)