diff --git a/uts/docs/writing-test-specs.md b/uts/docs/writing-test-specs.md index aa403e6b2..4e9ba577c 100644 --- a/uts/docs/writing-test-specs.md +++ b/uts/docs/writing-test-specs.md @@ -135,7 +135,7 @@ mock_http = MockHttpClient( onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { captured_request = req - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -212,7 +212,7 @@ connection.respond_with_success() # Wait for and handle request request = AWAIT mock_http.await_request() ASSERT request.headers["X-Ably-Version"] IS NOT null -request.respond_with(200, {"time": 1234567890000}) +request.respond_with(200, [1234567890000]) # Complete operation result = AWAIT request_future @@ -1034,7 +1034,7 @@ onRequest: (req) => { ```pseudo onRequest: (req) => { IF req.url.path CONTAINS "/time": - req.respond_with(200, {"time": ...}) + req.respond_with(200, [...]) ELSE IF req.url.path CONTAINS "/channels": req.respond_with(200, [...]) } diff --git a/uts/rest/unit/auth/authorize.md b/uts/rest/unit/auth/authorize.md index dc7937765..a43749835 100644 --- a/uts/rest/unit/auth/authorize.md +++ b/uts/rest/unit/auth/authorize.md @@ -376,7 +376,7 @@ mock_http = MockHttpClient( captured_requests.append(req) IF req.url.path == "/time": # Time query - req.respond_with(200, { "time": 1234567890000 }) + req.respond_with(200, [1234567890000]) ELSE: # Token request req.respond_with(200, { diff --git a/uts/rest/unit/fallback.md b/uts/rest/unit/fallback.md index 2a3f3df08..e345ed6d9 100644 --- a/uts/rest/unit/fallback.md +++ b/uts/rest/unit/fallback.md @@ -7,12 +7,7 @@ Unit test with mocked HTTP client ## Mock HTTP Infrastructure -See `rest_client.md` for the full Mock HTTP Infrastructure specification. These tests use the same `MockHttpClient` interface with `PendingConnection` and `PendingRequest`. - -Fallback tests require the mock to support: -- Connection-level failures (DNS, connection refused, timeout) -- Per-host or per-request response configuration -- Tracking multiple sequential requests to different hosts +See `helpers/mock_http.md` for the full Mock HTTP Infrastructure specification. These tests use the same `MockHttpClient` interface with `PendingConnection` and `PendingRequest`. --- @@ -26,8 +21,16 @@ Tests that fallback behavior is skipped when no fallback hosts are configured. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -39,7 +42,7 @@ client = Rest(options: ClientOptions( ```pseudo AWAIT client.time() FAILS WITH error # Should fail without retry -ASSERT mock_http.captured_requests.length == 1 +ASSERT captured_requests.length == 1 ASSERT error.statusCode == 500 ``` @@ -55,13 +58,17 @@ Tests that fallback hosts are tried when primary fails, in random order. ### Setup ```pseudo -mock_http = MockHttpClient() +captured_requests = [] + # All requests fail to test full fallback sequence -mock_http.queue_responses( - count: 6, # primary + 5 fallbacks - status: 500, - body: { "error": { "code": 50000 } } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -74,7 +81,7 @@ AWAIT client.time() FAILS WITH error ### Assertions ```pseudo -requests = mock_http.captured_requests +requests = captured_requests # First request to primary ASSERT requests[0].url.host == "main.realtime.ably.net" @@ -99,81 +106,6 @@ ASSERT ALL host IN fallback_hosts_used: host IN expected_fallbacks --- -## RSC15l - Qualifying errors trigger fallback - -**Test ID**: `rest/unit/RSC15l/qualifying-errors-trigger-fallback-0` - -| Spec | Requirement | -|------|-------------| -| RSC15l1 | Host unreachable errors trigger fallback | -| RSC15l2 | Request timeout errors trigger fallback | -| RSC15l3 | HTTP 5xx status codes (500-504) trigger fallback | - -Tests that specific error conditions trigger fallback retry. - -### Test Cases - -| ID | Spec | Condition | Should Retry | -|----|------|-----------|--------------| -| 1 | RSC15l1 | Host unreachable | Yes | -| 2 | RSC15l2 | Request timeout | Yes | -| 3 | RSC15l3 | HTTP 500 | Yes | -| 4 | RSC15l3 | HTTP 501 | Yes | -| 5 | RSC15l3 | HTTP 502 | Yes | -| 6 | RSC15l3 | HTTP 503 | Yes | -| 7 | RSC15l3 | HTTP 504 | Yes | -| 8 | | HTTP 400 | No | -| 9 | | HTTP 401 | No | -| 10 | | HTTP 404 | No | - -### Setup (HTTP status codes) -```pseudo -FOR EACH test_case IN [500, 501, 502, 503, 504]: - mock_http = MockHttpClient() - mock_http.queue_response(test_case, { "error": { "code": test_case * 100 } }) - mock_http.queue_response(200, { "time": 1234567890000 }) - - client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) - - AWAIT client.time() - - ASSERT mock_http.captured_requests.length == 2 - ASSERT mock_http.captured_requests[1].url.host != mock_http.captured_requests[0].url.host -``` - -### Setup (Non-retryable errors) -```pseudo -FOR EACH test_case IN [400, 401, 404]: - mock_http = MockHttpClient() - mock_http.queue_response(test_case, { "error": { "code": test_case * 100 } }) - - client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) - - AWAIT client.time() FAILS WITH error - # Expected to fail - - # Should NOT have retried - ASSERT mock_http.captured_requests.length == 1 -``` - -### Setup (Timeout) -```pseudo -mock_http = MockHttpClient() -mock_http.queue_timeout() # Simulates timeout -mock_http.queue_response(200, { "time": 1234567890000 }) - -client = Rest(options: ClientOptions( - key: "appId.keyId:keySecret", - httpRequestTimeout: 1000 -)) - -AWAIT client.time() - -ASSERT mock_http.captured_requests.length == 2 -``` - ---- - ## RSC15l4 - CloudFront errors trigger fallback **Test ID**: `rest/unit/RSC15l4/cloudfront-error-triggers-fallback-0` @@ -184,12 +116,24 @@ Tests that responses with CloudFront server header and status >= 400 trigger fal ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(403, - body: { "error": "Forbidden" }, - headers: { "Server": "CloudFront" } +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(403, + body: { "error": { "message": "Forbidden", "code": 40300, "statusCode": 403 } }, + headers: { "Server": "CloudFront" } + ) + ELSE: + req.respond_with(200, [1234567890000]) + } ) -mock_http.queue_response(200, { "time": 1234567890000 }) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -201,9 +145,9 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" -ASSERT mock_http.captured_requests[1].url.host != "main.realtime.ably.net" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests[1].url.host != "main.realtime.ably.net" ``` --- @@ -230,7 +174,7 @@ mock_http = MockHttpClient( conn.respond_with_success() }, onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -262,7 +206,7 @@ mock_http = MockHttpClient( conn.respond_with_success() }, onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -293,7 +237,7 @@ mock_http = MockHttpClient( conn.respond_with_success() }, onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -329,7 +273,7 @@ mock_http = MockHttpClient( req.respond_with_timeout() ELSE: # Fallback succeeds - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -362,7 +306,7 @@ FOR EACH status_code IN [500, 501, 502, 503, 504]: IF request_count == 1: req.respond_with(status_code, {"error": {"code": status_code * 100}}) ELSE: - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -413,9 +357,21 @@ Tests that the Host header is set correctly for fallback requests. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -427,8 +383,8 @@ AWAIT client.time() ### Assertions ```pseudo -request_1 = mock_http.captured_requests[0] -request_2 = mock_http.captured_requests[1] +request_1 = captured_requests[0] +request_2 = captured_requests[1] # Host header should match the actual host being requested ASSERT request_1.headers["Host"] == request_1.url.host @@ -448,13 +404,24 @@ Tests that after successful fallback, that host is used for subsequent requests. ### Setup ```pseudo -mock_http = MockHttpClient() -# First request to primary fails -mock_http.queue_response_for_host("main.realtime.ably.net", 500, { "error": {} }) -# First fallback succeeds -mock_http.queue_response_for_host("main.a.fallback.ably-realtime.com", 200, { "time": 1000 }) -# Second request should go directly to cached fallback -mock_http.queue_response_for_host("main.a.fallback.ably-realtime.com", 200, { "time": 2000 }) +captured_requests = [] + +# Primary fails; any fallback succeeds. The chosen fallback is observed +# from the captured requests rather than assumed, since RSC15a picks it +# in random order. +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + IF req.url.host == "main.realtime.ably.net": + # First request to primary fails + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + # Fallback succeeds + req.respond_with(200, [1000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -473,16 +440,18 @@ result2 = AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 3 +ASSERT captured_requests.length == 3 # Request 1: primary (failed) -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" # Request 2: fallback (succeeded) -ASSERT mock_http.captured_requests[1].url.host == "main.a.fallback.ably-realtime.com" +ASSERT captured_requests[1].url.host != "main.realtime.ably.net" + +fallback_host = captured_requests[1].url.host # Request 3: cached fallback (no retry to primary) -ASSERT mock_http.captured_requests[2].url.host == "main.a.fallback.ably-realtime.com" +ASSERT captured_requests[2].url.host == fallback_host ``` --- @@ -498,11 +467,26 @@ Tests that cached fallback host is cleared after `fallbackRetryTimeout`. ### Setup ```pseudo enable_fake_timers() -mock_http = MockHttpClient() -mock_http.queue_response_for_host("main.realtime.ably.net", 500, { "error": {} }) -mock_http.queue_response_for_host("main.a.fallback.ably-realtime.com", 200, { "time": 1000 }) -# After timeout, primary should be tried again -mock_http.queue_response_for_host("main.realtime.ably.net", 200, { "time": 2000 }) +captured_requests = [] +primary_request_count = 0 + +# Primary fails on its first attempt (triggering fallback) but succeeds +# on subsequent attempts; any fallback succeeds. +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + IF req.url.host == "main.realtime.ably.net": + primary_request_count++ + IF primary_request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [2000]) + ELSE: + req.respond_with(200, [1000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -524,10 +508,10 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 3 +ASSERT captured_requests.length == 3 # After timeout, primary is tried again -ASSERT mock_http.captured_requests[2].url.host == "main.realtime.ably.net" +ASSERT captured_requests[2].url.host == "main.realtime.ably.net" ``` --- @@ -543,28 +527,34 @@ Tests that a request that completes successfully against a fallback *after* `fal ### Setup ```pseudo enable_fake_timers() -mock_http = MockHttpClient() # Request handler: primary fails on first attempt, all others succeed. # Second request (to cached fallback) is NOT responded to immediately — # we hold the PendingRequest and respond later, after the timeout expires. held_request = null request_index = 0 +captured_requests = [] -mock_http.onRequest = (req) => - request_index += 1 - if request_index == 1 - # First request to primary — fail to trigger fallback - req.respond_with(500, { "error": { "message": "fail", "code": 50000, "statusCode": 500 } }) - else if request_index == 2 - # First fallback — succeed, caches this host - req.respond_with(200, [1000]) - else if request_index == 3 - # Second request goes to cached fallback — hold it (don't respond yet) - held_request = req - else - # All subsequent requests — succeed - req.respond_with(200, [1000]) +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_index += 1 + if request_index == 1 + # First request to primary — fail to trigger fallback + req.respond_with(500, { "error": { "message": "fail", "code": 50000, "statusCode": 500 } }) + else if request_index == 2 + # First fallback — succeed, caches this host + req.respond_with(200, [1000]) + else if request_index == 3 + # Second request goes to cached fallback — hold it (don't respond yet) + held_request = req + else + # All subsequent requests — succeed + req.respond_with(200, [1000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -597,22 +587,22 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 5 +ASSERT captured_requests.length == 5 # Requests 1+2: primary fail → fallback success -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" -ASSERT mock_http.captured_requests[1].url.host != "main.realtime.ably.net" +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests[1].url.host != "main.realtime.ably.net" -fallback_host = mock_http.captured_requests[1].url.host +fallback_host = captured_requests[1].url.host # Request 3: went to cached fallback (held, not yet responded) -ASSERT mock_http.captured_requests[2].url.host == fallback_host +ASSERT captured_requests[2].url.host == fallback_host # Request 4: after timeout expiry, primary is tried again -ASSERT mock_http.captured_requests[3].url.host == "main.realtime.ably.net" +ASSERT captured_requests[3].url.host == "main.realtime.ably.net" # Request 5: late success from request 3 did NOT re-pin fallback -ASSERT mock_http.captured_requests[4].url.host == "main.realtime.ably.net" +ASSERT captured_requests[4].url.host == "main.realtime.ably.net" ``` --- @@ -631,8 +621,16 @@ Tests that the default primary domain is used when no endpoint options are speci ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -644,7 +642,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" ``` --- @@ -657,8 +655,16 @@ Tests that when `endpoint` contains a period (`.`), it's treated as an explicit ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -673,7 +679,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "custom.ably.example.com" +ASSERT captured_requests[0].url.host == "custom.ably.example.com" ``` --- @@ -686,8 +692,16 @@ Tests that `endpoint: "localhost"` is treated as an explicit hostname. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -702,7 +716,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "localhost" +ASSERT captured_requests[0].url.host == "localhost" ``` --- @@ -715,8 +729,16 @@ Tests that `endpoint` containing `::` is treated as an explicit hostname (IPv6). ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -732,8 +754,8 @@ AWAIT client.time() ### Assertions ```pseudo # IPv6 addresses may be bracketed in URLs -ASSERT mock_http.captured_requests[0].url.host == "::1" OR - mock_http.captured_requests[0].url.host == "[::1]" +ASSERT captured_requests[0].url.host == "::1" OR + captured_requests[0].url.host == "[::1]" ``` --- @@ -746,8 +768,16 @@ Tests that `endpoint: "nonprod:[id]"` resolves to `[id].realtime.ably-nonprod.ne ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -762,7 +792,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "staging.realtime.ably-nonprod.net" +ASSERT captured_requests[0].url.host == "staging.realtime.ably-nonprod.net" ``` --- @@ -775,8 +805,16 @@ Tests that `endpoint: "[id]"` (without period or nonprod prefix) resolves to `[i ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -791,7 +829,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "test.realtime.ably.net" +ASSERT captured_requests[0].url.host == "test.realtime.ably.net" ``` --- @@ -896,8 +934,16 @@ Tests that the deprecated `environment` option sets primary domain to `[id].real ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -912,7 +958,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "sandbox.realtime.ably.net" +ASSERT captured_requests[0].url.host == "sandbox.realtime.ably.net" ``` --- @@ -971,8 +1017,16 @@ Tests that the deprecated `restHost` option sets the primary domain. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -987,7 +1041,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "custom.rest.example.com" +ASSERT captured_requests[0].url.host == "custom.rest.example.com" ``` --- @@ -1000,8 +1054,16 @@ Tests that `realtimeHost` sets primary domain when `restHost` is not specified. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1016,7 +1078,7 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests[0].url.host == "custom.realtime.example.com" +ASSERT captured_requests[0].url.host == "custom.realtime.example.com" ``` --- @@ -1029,8 +1091,16 @@ Tests that when both `restHost` and `realtimeHost` are specified, `restHost` is ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1047,7 +1117,7 @@ AWAIT client.time() ### Assertions ```pseudo # REST client uses restHost, not realtimeHost -ASSERT mock_http.captured_requests[0].url.host == "rest.example.com" +ASSERT captured_requests[0].url.host == "rest.example.com" ``` --- @@ -1066,11 +1136,23 @@ Tests that default configuration provides the standard fallback domains. ### Setup ```pseudo -mock_http = MockHttpClient() -# Primary fails -mock_http.queue_response(500, { "error": { "code": 50000 } }) -# Fallback succeeds -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + # Primary fails + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + # Fallback succeeds + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -1082,8 +1164,8 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" expected_fallbacks = [ "main.a.fallback.ably-realtime.com", @@ -1092,7 +1174,7 @@ expected_fallbacks = [ "main.d.fallback.ably-realtime.com", "main.e.fallback.ably-realtime.com" ] -ASSERT mock_http.captured_requests[1].url.host IN expected_fallbacks +ASSERT captured_requests[1].url.host IN expected_fallbacks ``` --- @@ -1105,9 +1187,21 @@ Tests that the `fallbackHosts` option overrides default fallbacks. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1122,9 +1216,9 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "main.realtime.ably.net" -ASSERT mock_http.captured_requests[1].url.host IN ["fb1.example.com", "fb2.example.com", "fb3.example.com"] +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "main.realtime.ably.net" +ASSERT captured_requests[1].url.host IN ["fb1.example.com", "fb2.example.com", "fb3.example.com"] ``` --- @@ -1160,9 +1254,21 @@ Tests that `fallbackHostsUseDefault: true` uses the default fallback domains. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1178,8 +1284,8 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "custom.host.com" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "custom.host.com" # Should use default fallbacks despite custom restHost expected_fallbacks = [ @@ -1189,7 +1295,7 @@ expected_fallbacks = [ "main.d.fallback.ably-realtime.com", "main.e.fallback.ably-realtime.com" ] -ASSERT mock_http.captured_requests[1].url.host IN expected_fallbacks +ASSERT captured_requests[1].url.host IN expected_fallbacks ``` --- @@ -1202,8 +1308,16 @@ Tests that when `endpoint` is an explicit hostname, fallback domains are empty. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1220,8 +1334,8 @@ AWAIT client.time() FAILS WITH error ### Assertions ```pseudo # No fallback attempted - only one request -ASSERT mock_http.captured_requests.length == 1 -ASSERT mock_http.captured_requests[0].url.host == "custom.ably.example.com" +ASSERT captured_requests.length == 1 +ASSERT captured_requests[0].url.host == "custom.ably.example.com" ``` --- @@ -1234,9 +1348,21 @@ Tests that nonprod routing policy has corresponding nonprod fallback domains. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1251,8 +1377,8 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "staging.realtime.ably-nonprod.net" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "staging.realtime.ably-nonprod.net" expected_fallbacks = [ "staging.a.fallback.ably-realtime-nonprod.com", @@ -1261,7 +1387,7 @@ expected_fallbacks = [ "staging.d.fallback.ably-realtime-nonprod.com", "staging.e.fallback.ably-realtime-nonprod.com" ] -ASSERT mock_http.captured_requests[1].url.host IN expected_fallbacks +ASSERT captured_requests[1].url.host IN expected_fallbacks ``` --- @@ -1274,9 +1400,21 @@ Tests that production routing policy via `endpoint` has corresponding fallback d ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1291,8 +1429,8 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "test.realtime.ably.net" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "test.realtime.ably.net" expected_fallbacks = [ "test.a.fallback.ably-realtime.com", @@ -1301,7 +1439,7 @@ expected_fallbacks = [ "test.d.fallback.ably-realtime.com", "test.e.fallback.ably-realtime.com" ] -ASSERT mock_http.captured_requests[1].url.host IN expected_fallbacks +ASSERT captured_requests[1].url.host IN expected_fallbacks ``` --- @@ -1314,9 +1452,21 @@ Tests that production routing policy via deprecated `environment` has correspond ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1331,8 +1481,8 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[0].url.host == "sandbox.realtime.ably.net" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[0].url.host == "sandbox.realtime.ably.net" expected_fallbacks = [ "sandbox.a.fallback.ably-realtime.com", @@ -1341,7 +1491,7 @@ expected_fallbacks = [ "sandbox.d.fallback.ably-realtime.com", "sandbox.e.fallback.ably-realtime.com" ] -ASSERT mock_http.captured_requests[1].url.host IN expected_fallbacks +ASSERT captured_requests[1].url.host IN expected_fallbacks ``` --- @@ -1354,8 +1504,16 @@ Tests that deprecated `restHost` option results in no fallback domains. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1372,8 +1530,8 @@ AWAIT client.time() FAILS WITH error ### Assertions ```pseudo # No fallback attempted -ASSERT mock_http.captured_requests.length == 1 -ASSERT mock_http.captured_requests[0].url.host == "custom.rest.example.com" +ASSERT captured_requests.length == 1 +ASSERT captured_requests[0].url.host == "custom.rest.example.com" ``` --- @@ -1386,8 +1544,16 @@ Tests that deprecated `realtimeHost` option results in no fallback domains. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, { "error": { "code": 50000 } }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1404,8 +1570,8 @@ AWAIT client.time() FAILS WITH error ### Assertions ```pseudo # No fallback attempted -ASSERT mock_http.captured_requests.length == 1 -ASSERT mock_http.captured_requests[0].url.host == "custom.realtime.example.com" +ASSERT captured_requests.length == 1 +ASSERT captured_requests[0].url.host == "custom.realtime.example.com" ``` --- @@ -1423,13 +1589,19 @@ This test is primarily relevant for Realtime clients that perform connectivity c ### Setup ```pseudo -mock_http = MockHttpClient() -# Queue response for connectivity check -mock_http.queue_response_for_url( - "https://internet-up.ably-realtime.com/is-the-internet-up.txt", - 200, - "yes" +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + IF req.url.toString() == "https://internet-up.ably-realtime.com/is-the-internet-up.txt": + req.respond_with(200, "yes") + ELSE: + req.respond_with(404, {"error": {"code": 40400}}) + } ) +install_mock(mock_http) client = Realtime(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -1444,7 +1616,7 @@ result = AWAIT client.connection.checkConnectivity() ### Assertions ```pseudo -connectivity_requests = mock_http.captured_requests.filter( +connectivity_requests = captured_requests.filter( r => r.url.path CONTAINS "is-the-internet-up" ) ASSERT connectivity_requests.length >= 1 @@ -1463,12 +1635,19 @@ Tests that the `connectivityCheckUrl` option overrides the default. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response_for_url( - "https://custom.example.com/connectivity", - 200, - "ok" +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + IF req.url.toString() == "https://custom.example.com/connectivity": + req.respond_with(200, "ok") + ELSE: + req.respond_with(404, {"error": {"code": 40400}}) + } ) +install_mock(mock_http) client = Realtime(options: ClientOptions( key: "appId.keyId:keySecret", @@ -1483,14 +1662,14 @@ result = AWAIT client.connection.checkConnectivity() ### Assertions ```pseudo -connectivity_requests = mock_http.captured_requests.filter( +connectivity_requests = captured_requests.filter( r => r.url.host == "custom.example.com" ) ASSERT connectivity_requests.length >= 1 ASSERT connectivity_requests[0].url.toString() == "https://custom.example.com/connectivity" # Should NOT request the default URL -default_requests = mock_http.captured_requests.filter( +default_requests = captured_requests.filter( r => r.url.host == "internet-up.ably-realtime.com" ) ASSERT default_requests.length == 0 @@ -1518,12 +1697,16 @@ Tests that the connectivity check expects a specific response. ### Setup (Case 1 - Success) ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response_for_url( - "https://internet-up.ably-realtime.com/is-the-internet-up.txt", - 200, - "yes" +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF req.url.toString() == "https://internet-up.ably-realtime.com/is-the-internet-up.txt": + req.respond_with(200, "yes") + ELSE: + req.respond_with(404, {"error": {"code": 40400}}) + } ) +install_mock(mock_http) client = Realtime(options: ClientOptions(key: "appId.keyId:keySecret")) result = AWAIT client.connection.checkConnectivity() @@ -1535,12 +1718,16 @@ CLOSE_CLIENT(client) ### Setup (Case 2 - Wrong body) ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response_for_url( - "https://internet-up.ably-realtime.com/is-the-internet-up.txt", - 200, - "no" +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF req.url.toString() == "https://internet-up.ably-realtime.com/is-the-internet-up.txt": + req.respond_with(200, "no") + ELSE: + req.respond_with(404, {"error": {"code": 40400}}) + } ) +install_mock(mock_http) client = Realtime(options: ClientOptions(key: "appId.keyId:keySecret")) result = AWAIT client.connection.checkConnectivity() @@ -1552,12 +1739,16 @@ CLOSE_CLIENT(client) ### Setup (Case 4 - HTTP error) ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response_for_url( - "https://internet-up.ably-realtime.com/is-the-internet-up.txt", - 404, - "Not Found" +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF req.url.toString() == "https://internet-up.ably-realtime.com/is-the-internet-up.txt": + req.respond_with(404, "Not Found") + ELSE: + req.respond_with(404, {"error": {"code": 40400}}) + } ) +install_mock(mock_http) client = Realtime(options: ClientOptions(key: "appId.keyId:keySecret")) result = AWAIT client.connection.checkConnectivity() diff --git a/uts/rest/unit/helpers/mock_http.md b/uts/rest/unit/helpers/mock_http.md index 6cd99961a..78a004b8c 100644 --- a/uts/rest/unit/helpers/mock_http.md +++ b/uts/rest/unit/helpers/mock_http.md @@ -67,7 +67,7 @@ mock_http = MockHttpClient( }, onRequest: (req) => { IF req.url.path == "/time": - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) ELSE: req.respond_with(404, {"error": {"code": 40400}}) } @@ -97,7 +97,7 @@ mock_http = MockHttpClient( onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { captured_requests.append(req) - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -122,7 +122,7 @@ mock_http = MockHttpClient( IF request_count == 1: req.respond_with(500, {"error": {"code": 50000}}) ELSE: - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -153,7 +153,7 @@ connection.respond_with_success() # Wait for and handle HTTP request request = AWAIT mock_http.await_request() ASSERT request.headers["X-Ably-Version"] IS NOT null -request.respond_with(200, {"time": 1234567890000}) +request.respond_with(200, [1234567890000]) # Complete the operation result = AWAIT request_future diff --git a/uts/rest/unit/request.md b/uts/rest/unit/request.md index 4971775f7..a3732745e 100644 --- a/uts/rest/unit/request.md +++ b/uts/rest/unit/request.md @@ -7,13 +7,13 @@ Unit test with mocked HTTP client ## Mock Configuration -These tests use the mock HTTP infrastructure defined in `rest_client.md`. The mock supports: +These tests use the mock HTTP infrastructure defined in `helpers/mock_http.md`. The mock supports: - Handler-based configuration with `onConnectionAttempt` and `onRequest` - Capturing requests via `captured_requests` arrays - Configurable responses with status codes, bodies, and headers - Await-based API for coordinating test responses -See `rest_client.md` for detailed mock interface documentation. +See `helpers/mock_http.md` for detailed mock interface documentation. ## Overview @@ -208,8 +208,16 @@ Tests that the version parameter sets the X-Ably-Version header. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, []) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, []) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -224,12 +232,11 @@ client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - mock_http.queue_response(200, []) + captured_requests = [] response = AWAIT client.request("GET", "/test", version: test_case.version) - request = mock_http.captured_requests[0] + request = captured_requests[0] ASSERT request.headers["X-Ably-Version"] == test_case.expected_header ``` @@ -319,8 +326,16 @@ Tests that Accept and Content-Type headers reflect the configured protocol. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, []) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, []) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -338,7 +353,7 @@ response = AWAIT client.request("POST", "/test", ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] ASSERT request.headers["Accept"] == "application/json" ASSERT request.headers["Content-Type"] == "application/json" ``` @@ -353,8 +368,16 @@ Tests that Accept and Content-Type headers reflect MsgPack protocol when configu ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, msgpack_encode([])) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, msgpack_encode([])) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -372,7 +395,7 @@ response = AWAIT client.request("POST", "/test", ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] ASSERT request.headers["Accept"] == "application/x-msgpack" ASSERT request.headers["Content-Type"] == "application/x-msgpack" ``` @@ -389,8 +412,16 @@ Tests that the request body is encoded using the configured protocol. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, []) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, []) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -408,7 +439,7 @@ response = AWAIT client.request("POST", "/test", ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] # Body should be valid JSON body = json_decode(request.body) ASSERT body["name"] == "event" @@ -419,8 +450,16 @@ ASSERT body["data"]["nested"] == "value" ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, msgpack_encode([])) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, msgpack_encode([])) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -438,7 +477,7 @@ response = AWAIT client.request("POST", "/test", ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] # Body should be valid MsgPack body = msgpack_decode(request.body) ASSERT body["name"] == "event" @@ -457,11 +496,16 @@ Tests that the response body is automatically decoded based on Content-Type head ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, - body: json_encode([{ "id": "1", "name": "item1" }, { "id": "2", "name": "item2" }]), - headers: { "Content-Type": "application/json" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, + body: json_encode([{ "id": "1", "name": "item1" }, { "id": "2", "name": "item2" }]), + headers: { "Content-Type": "application/json" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -483,11 +527,16 @@ ASSERT items[1]["name"] == "item2" ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, - body: msgpack_encode([{ "id": "1" }]), - headers: { "Content-Type": "application/x-msgpack" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, + body: msgpack_encode([{ "id": "1" }]), + headers: { "Content-Type": "application/x-msgpack" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -517,11 +566,6 @@ ASSERT items[0]["id"] == "1" Tests that the response object provides access to the HTTP status code. -### Setup -```pseudo -mock_http = MockHttpClient() -``` - ### Test Cases | ID | Status Code | @@ -535,13 +579,20 @@ mock_http = MockHttpClient() ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - - IF test_case.status_code >= 400: - mock_http.queue_response(test_case.status_code, - { "error": { "code": test_case.status_code * 100, "message": "Error" } }) - ELSE: - mock_http.queue_response(test_case.status_code, []) + # A 500 is a qualifying error (RSC15l3) and triggers fallback retries; + # the handler answers every attempt with the same status so the final + # response carries the expected status code. + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF test_case.status_code >= 400: + req.respond_with(test_case.status_code, + { "error": { "message": "Error", "code": test_case.status_code * 100, "statusCode": test_case.status_code } }) + ELSE: + req.respond_with(test_case.status_code, []) + } + ) + install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) response = AWAIT client.request("GET", "/test", version: 3) @@ -557,12 +608,6 @@ FOR EACH test_case IN test_cases: Tests that the success property correctly reflects 2xx status codes. -### Setup -```pseudo -mock_http = MockHttpClient() -client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) -``` - ### Test Cases | ID | Status Code | Expected Success | @@ -578,14 +623,21 @@ client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - - IF test_case.status_code >= 400: - mock_http.queue_response(test_case.status_code, - { "error": { "code": test_case.status_code * 100, "message": "Error" } }) - ELSE: - mock_http.queue_response(test_case.status_code, []) + # A 500 is a qualifying error (RSC15l3) and triggers fallback retries; + # the handler answers every attempt with the same status. + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF test_case.status_code >= 400: + req.respond_with(test_case.status_code, + { "error": { "message": "Error", "code": test_case.status_code * 100, "statusCode": test_case.status_code } }) + ELSE: + req.respond_with(test_case.status_code, []) + } + ) + install_mock(mock_http) + client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) response = AWAIT client.request("GET", "/test", version: 3) ASSERT response.success == test_case.expected_success @@ -601,11 +653,16 @@ Tests that the errorCode property extracts the value from X-Ably-Errorcode heade ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(401, - body: { "error": { "code": 40101, "message": "Unauthorized" } }, - headers: { "X-Ably-Errorcode": "40101" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(401, + body: { "error": { "message": "Unauthorized", "code": 40101, "statusCode": 401 } }, + headers: { "X-Ably-Errorcode": "40101" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -630,14 +687,19 @@ Tests that the errorMessage property extracts the value from X-Ably-Errormessage ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(401, - body: { "error": { "code": 40101, "message": "Unauthorized" } }, - headers: { - "X-Ably-Errorcode": "40101", - "X-Ably-Errormessage": "Token expired" +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(401, + body: { "error": { "message": "Unauthorized", "code": 40101, "statusCode": 401 } }, + headers: { + "X-Ably-Errorcode": "40101", + "X-Ably-Errormessage": "Token expired" + } + ) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -662,15 +724,20 @@ Tests that all response headers are accessible. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, - body: [], - headers: { - "Content-Type": "application/json", - "X-Request-Id": "req-123", - "X-Custom-Header": "custom-value" +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, + body: [], + headers: { + "Content-Type": "application/json", + "X-Request-Id": "req-123", + "X-Custom-Header": "custom-value" + } + ) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -698,11 +765,16 @@ Tests that the items() method returns the decoded response body. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, [ - { "id": "msg1", "name": "event1", "data": "data1" }, - { "id": "msg2", "name": "event2", "data": "data2" } -]) +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, [ + { "id": "msg1", "name": "event1", "data": "data1" }, + { "id": "msg2", "name": "event2", "data": "data2" } + ]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -735,21 +807,26 @@ Tests that multi-page responses can be navigated using next(). ### Setup ```pseudo -mock_http = MockHttpClient() - -# First page -mock_http.queue_response(200, - body: [{ "id": "1" }, { "id": "2" }], - headers: { - "Link": '; rel="next"' +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + IF req.url.query_params["page"] == "2": + # Second page + req.respond_with(200, + body: [{ "id": "3" }], + headers: {} # No "next" link - last page + ) + ELSE: + # First page + req.respond_with(200, + body: [{ "id": "1" }, { "id": "2" }], + headers: { + "Link": '; rel="next"' + } + ) } ) - -# Second page -mock_http.queue_response(200, - body: [{ "id": "3" }], - headers: {} # No "next" link - last page -) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -784,23 +861,33 @@ Tests that non-array responses are handled correctly (wrapped as single item). ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +# The channel-status path returns a single JSON object (not an array), +# which is what this test exercises. +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, { + "channelId": "test", + "status": { "isActive": true } + }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` ### Test Steps ```pseudo -response = AWAIT client.request("GET", "/time", version: 3) +response = AWAIT client.request("GET", "/channels/test", version: 3) items = response.items() ``` ### Assertions ```pseudo -# Non-array response should be accessible -ASSERT items.length == 1 OR items["time"] == 1234567890000 -# Implementation may vary - either wrap in array or return object directly +# A single (non-array) JSON object response is wrapped as one item +ASSERT items.length == 1 +ASSERT items[0]["channelId"] == "test" ``` --- @@ -842,12 +929,13 @@ Tests that request timeouts are properly handled. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_delayed_response( - delay: 5000, # 5 second delay - status: 200, - body: [] +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with_delay(5000, 200, []) # 5 second delay + } ) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -872,11 +960,19 @@ Tests that HTTP error responses (4xx, 5xx with valid Ably error body) are return ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(400, - body: { "error": { "code": 40000, "message": "Bad request" } }, - headers: { "X-Ably-Errorcode": "40000" } +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(400, + body: { "error": { "message": "Bad request", "code": 40000, "statusCode": 400 } }, + headers: { "X-Ably-Errorcode": "40000" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -897,7 +993,7 @@ ASSERT response.success == false ASSERT response.errorCode == 40000 # Only one request should have been made (no fallback) -ASSERT mock_http.captured_requests.length == 1 +ASSERT captured_requests.length == 1 ``` --- @@ -910,16 +1006,26 @@ Tests that fallback hosts are attempted when primary host returns server error w ### Setup ```pseudo -mock_http = MockHttpClient() +request_count = 0 +captured_requests = [] -# Primary host fails with non-Ably 500 error -mock_http.queue_response(500, - body: "Internal Server Error", - headers: { "Content-Type": "text/plain" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + # Primary host fails with non-Ably 500 error + req.respond_with(500, + body: "Internal Server Error", + headers: { "Content-Type": "text/plain" } + ) + ELSE: + # Fallback succeeds + req.respond_with(200, [{ "id": "1" }]) + } ) - -# Fallback succeeds -mock_http.queue_response(200, [{ "id": "1" }]) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -938,8 +1044,8 @@ ASSERT response.statusCode == 200 ASSERT response.success == true # Two requests: primary failed, fallback succeeded -ASSERT mock_http.captured_requests.length == 2 -ASSERT mock_http.captured_requests[1].url.host == "fallback.ably-realtime.com" +ASSERT captured_requests.length == 2 +ASSERT captured_requests[1].url.host == "fallback.ably-realtime.com" ``` --- @@ -952,8 +1058,16 @@ Tests that the request() method does not allow overriding the configured authent ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, []) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, []) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -969,7 +1083,7 @@ response = AWAIT client.request("GET", "/test", ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] # The configured Basic auth should be used, not the custom header ASSERT request.headers["Authorization"] STARTS_WITH "Basic " @@ -990,7 +1104,17 @@ Tests that paths are handled correctly whether or not they include a leading sla ### Setup ```pseudo -mock_http = MockHttpClient() +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, []) + } +) +install_mock(mock_http) + client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -1004,12 +1128,11 @@ client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - mock_http.queue_response(200, []) + captured_requests = [] response = AWAIT client.request("GET", test_case.path, version: 3) - request = mock_http.captured_requests[0] + request = captured_requests[0] ASSERT request.url.path == test_case.expected_path ``` @@ -1023,11 +1146,16 @@ Tests that empty responses (204 No Content) are handled correctly. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(204, - body: null, # No body - headers: {} +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(204, + body: null, # No body + headers: {} + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` diff --git a/uts/rest/unit/request_endpoint.md b/uts/rest/unit/request_endpoint.md index 258749eb0..eef1dd33a 100644 --- a/uts/rest/unit/request_endpoint.md +++ b/uts/rest/unit/request_endpoint.md @@ -23,11 +23,16 @@ Tests that REST requests are sent to the default primary domain when no endpoint #### Setup ```pseudo +captured_requests = [] + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + captured_requests.append(req) + req.respond_with(200, [1234567890000]) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -39,8 +44,8 @@ AWAIT client.time() #### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 1 -ASSERT mock_http.captured_requests[0].url.host == DEFAULT_REST_HOST +ASSERT captured_requests.length == 1 +ASSERT captured_requests[0].url.host == DEFAULT_REST_HOST ``` --- @@ -53,11 +58,16 @@ Tests that REST requests are sent to a custom production routing policy domain. #### Setup ```pseudo +captured_requests = [] + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + captured_requests.append(req) + req.respond_with(200, [1234567890000]) } ) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -72,8 +82,8 @@ AWAIT client.time() #### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 1 -ASSERT mock_http.captured_requests[0].url.host == "test.realtime.ably.net" +ASSERT captured_requests.length == 1 +ASSERT captured_requests[0].url.host == "test.realtime.ably.net" ``` --- @@ -86,11 +96,16 @@ Tests that successive requests continue to use the primary domain (no unexpected #### Setup ```pseudo +captured_requests = [] + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { - req.respond_with(200, {"time": 1234567890000}) + captured_requests.append(req) + req.respond_with(200, [1234567890000]) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -104,8 +119,8 @@ AWAIT client.time() #### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 3 -FOR EACH request IN mock_http.captured_requests: +ASSERT captured_requests.length == 3 +FOR EACH request IN captured_requests: ASSERT request.url.host == DEFAULT_REST_HOST ``` @@ -120,16 +135,20 @@ Tests that when the primary host fails and a fallback succeeds, the primary was #### Setup ```pseudo request_count = 0 +captured_requests = [] mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { + captured_requests.append(req) request_count++ IF request_count == 1: - req.respond_with(500, {"error": {"code": 50000}}) + req.respond_with(500, {"error": {"message": "Internal error", "code": 50000, "statusCode": 500}}) ELSE: - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -141,11 +160,11 @@ AWAIT client.time() #### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 +ASSERT captured_requests.length == 2 # First request was to primary domain -ASSERT mock_http.captured_requests[0].url.host == DEFAULT_REST_HOST +ASSERT captured_requests[0].url.host == DEFAULT_REST_HOST # Second request was to a fallback domain (not primary) -ASSERT mock_http.captured_requests[1].url.host != DEFAULT_REST_HOST +ASSERT captured_requests[1].url.host != DEFAULT_REST_HOST ``` --- @@ -158,11 +177,16 @@ Tests that the request path and query parameters are correctly constructed when #### Setup ```pseudo +captured_requests = [] + mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { + captured_requests.append(req) req.respond_with(200, []) } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -174,8 +198,8 @@ AWAIT client.channels.get("test-channel").history() #### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 1 -request = mock_http.captured_requests[0] +ASSERT captured_requests.length == 1 +request = captured_requests[0] ASSERT request.url.host == DEFAULT_REST_HOST ASSERT request.url.path == "/channels/test-channel/messages" ASSERT request.method == "GET" diff --git a/uts/rest/unit/rest_client.md b/uts/rest/unit/rest_client.md index 20986fd2b..f752d57ca 100644 --- a/uts/rest/unit/rest_client.md +++ b/uts/rest/unit/rest_client.md @@ -48,7 +48,7 @@ mock_http = MockHttpClient( onConnectionAttempt: (conn) => conn.respond_with_success(), onRequest: (req) => { captured_request = req - req.respond_with(200, {"time": 1234567890000}) + req.respond_with(200, [1234567890000]) } ) install_mock(mock_http) @@ -84,8 +84,16 @@ Tests that all REST requests include the `Ably-Agent` header with correct format ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -97,7 +105,7 @@ AWAIT client.time() ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] ASSERT "Ably-Agent" IN request.headers agent = request.headers["Ably-Agent"] @@ -119,8 +127,16 @@ Tests that `request_id` query parameter is included when `addRequestIds` is true ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -135,7 +151,7 @@ AWAIT client.time() ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] ASSERT "request_id" IN request.url.query_params request_id = request.url.query_params["request_id"] @@ -156,11 +172,23 @@ Tests that the same `request_id` is used when retrying to a fallback host. ### Setup ```pseudo -mock_http = MockHttpClient() -# First request fails with 500 (triggers fallback retry) -mock_http.queue_response(500, { "error": { "code": 50000 } }) -# Retry succeeds -mock_http.queue_response(200, { "time": 1234567890000 }) +request_count = 0 +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + request_count++ + IF request_count == 1: + # First request fails with 500 (triggers fallback retry) + req.respond_with(500, { "error": { "message": "Internal error", "code": 50000, "statusCode": 500 } }) + ELSE: + # Retry succeeds + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -176,10 +204,10 @@ AWAIT client.time() ### Assertions ```pseudo -ASSERT mock_http.captured_requests.length == 2 +ASSERT captured_requests.length == 2 -request_id_1 = mock_http.captured_requests[0].url.query_params["request_id"] -request_id_2 = mock_http.captured_requests[1].url.query_params["request_id"] +request_id_1 = captured_requests[0].url.query_params["request_id"] +request_id_2 = captured_requests[1].url.query_params["request_id"] ASSERT request_id_1 == request_id_2 # Same ID for retry ``` @@ -201,7 +229,16 @@ Tests that the correct protocol (MessagePack or JSON) is used based on configura ### Setup ```pseudo -mock_http = MockHttpClient() +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(201, { "serials": ["s1"] }) + } +) +install_mock(mock_http) ``` ### Test Cases @@ -214,8 +251,7 @@ mock_http = MockHttpClient() ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - mock_http.queue_response(201, { "serials": ["s1"] }) + captured_requests = [] client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -224,7 +260,7 @@ FOR EACH test_case IN test_cases: AWAIT client.channels.get("test").publish(name: "e", data: "d") - request = mock_http.captured_requests[0] + request = captured_requests[0] ASSERT request.headers["Content-Type"] == test_case.expected_content_type ASSERT request.headers["Accept"] == test_case.expected_content_type ``` @@ -241,8 +277,16 @@ Tests that Accept and Content-Type headers reflect the configured protocol. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(201, { "serials": ["s1"] }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(201, { "serials": ["s1"] }) + } +) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -257,7 +301,7 @@ AWAIT client.channels.get("test").publish(name: "e", data: "d") ### Assertions ```pseudo -request = mock_http.captured_requests[0] +request = captured_requests[0] ASSERT request.headers["Accept"] == "application/json" ASSERT request.headers["Content-Type"] == "application/json" ``` @@ -274,12 +318,17 @@ Tests that responses with different Content-Type than requested are still proces ### Setup ```pseudo -mock_http = MockHttpClient() -# Client requests JSON but server returns msgpack -mock_http.queue_response(200, - body: msgpack_encode({ "time": 1234567890000 }), - headers: { "Content-Type": "application/x-msgpack" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + # Client requests JSON but server returns msgpack + req.respond_with(200, + body: msgpack_encode([1234567890000]), + headers: { "Content-Type": "application/x-msgpack" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -317,11 +366,16 @@ Tests error handling when server returns unsupported Content-Type. ### Setup (Case 1 - Error status) ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(500, - body: "Server Error", - headers: { "Content-Type": "text/html" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(500, + body: "Server Error", + headers: { "Content-Type": "text/html" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -338,11 +392,16 @@ ASSERT error.statusCode == 500 ### Setup (Case 2 - Success status but bad content) ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, - body: "OK", - headers: { "Content-Type": "text/html" } +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + req.respond_with(200, + body: "OK", + headers: { "Content-Type": "text/html" } + ) + } ) +install_mock(mock_http) client = Rest(options: ClientOptions(key: "appId.keyId:keySecret")) ``` @@ -436,7 +495,7 @@ time_future = client.time() # Wait for request and respond with delay request = AWAIT mock_http.await_request() -request.respond_with_delay(5000, 200, {"time": 1234567890000}) +request.respond_with_delay(5000, 200, [1234567890000]) AWAIT time_future FAILS WITH error ASSERT error.code == 50003 OR error.message CONTAINS "timeout" @@ -520,15 +579,22 @@ Tests that TLS setting controls protocol used. ### Setup ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +captured_requests = [] + +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => { + captured_requests.append(req) + req.respond_with(200, [1234567890000]) + } +) +install_mock(mock_http) ``` ### Test Steps ```pseudo FOR EACH test_case IN test_cases: - mock_http.reset() - mock_http.queue_response(200, { "time": 1234567890000 }) + captured_requests = [] client = Rest(options: ClientOptions( key: "appId.keyId:keySecret", @@ -537,7 +603,7 @@ FOR EACH test_case IN test_cases: AWAIT client.time() - request = mock_http.captured_requests[0] + request = captured_requests[0] ASSERT request.url.scheme == test_case.expected_scheme ``` @@ -570,8 +636,11 @@ Token auth over HTTP should be allowed. Only Basic auth (API key) should be reje ### Additional Test - Token auth over HTTP allowed ```pseudo -mock_http = MockHttpClient() -mock_http.queue_response(200, { "time": 1234567890000 }) +mock_http = MockHttpClient( + onConnectionAttempt: (conn) => conn.respond_with_success(), + onRequest: (req) => req.respond_with(200, [1234567890000]) +) +install_mock(mock_http) client = Rest(options: ClientOptions( token: "some-token-string",