Skip to content

Commit 9bfa4db

Browse files
committed
fix: auth_url handling and add timeout to once_async calls in realtime tests
- Replaced all `connection.once_async` calls with `asyncio.wait_for` to include a 5-second timeout. - Ensures tests fail gracefully if connection isn't established within the specified timeframe.
1 parent 61cedb0 commit 9bfa4db

File tree

11 files changed

+227
-135
lines changed

11 files changed

+227
-135
lines changed

.github/workflows/check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030

3131
- name: Setup poetry
3232
uses: abatilo/actions-poetry@v4
33+
with:
34+
poetry-version: '1.8.5'
3335

3436
- name: Setup a local virtual environment
3537
run: |

ably/http/http.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ably.http.httputils import HttpUtils
1212
from ably.transport.defaults import Defaults
1313
from ably.util.exceptions import AblyException
14-
from ably.util.helper import is_token_error
14+
from ably.util.helper import is_token_error, extract_url_params
1515

1616
log = logging.getLogger(__name__)
1717

@@ -198,11 +198,13 @@ def should_stop_retrying():
198198
self.preferred_port)
199199
url = urljoin(base_url, path)
200200

201+
(clean_url, url_params) = extract_url_params(url)
202+
201203
request = self.__client.build_request(
202204
method=method,
203-
url=url,
205+
url=clean_url,
204206
content=body,
205-
params=params,
207+
params=dict(url_params, **params),
206208
headers=all_headers,
207209
timeout=timeout,
208210
)

ably/rest/auth.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from __future__ import annotations
2+
23
import base64
3-
from datetime import timedelta
44
import logging
55
import time
6-
from typing import Optional, TYPE_CHECKING, Union
76
import uuid
7+
from datetime import timedelta
8+
from typing import Optional, TYPE_CHECKING, Union
9+
810
import httpx
911

1012
from ably.types.options import Options
13+
1114
if TYPE_CHECKING:
1215
from ably.rest.rest import AblyRest
1316
from ably.realtime.realtime import AblyRealtime
@@ -16,14 +19,14 @@
1619
from ably.types.tokendetails import TokenDetails
1720
from ably.types.tokenrequest import TokenRequest
1821
from ably.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException
22+
from ably.util.helper import extract_url_params
1923

2024
__all__ = ["Auth"]
2125

2226
log = logging.getLogger(__name__)
2327

2428

2529
class Auth:
26-
2730
class Method:
2831
BASIC = "BASIC"
2932
TOKEN = "TOKEN"
@@ -271,8 +274,7 @@ async def create_token_request(self, token_params: Optional[dict | str] = None,
271274
if capability is not None:
272275
token_request['capability'] = str(Capability(capability))
273276

274-
token_request["client_id"] = (
275-
token_params.get('client_id') or self.client_id)
277+
token_request["client_id"] = token_params.get('client_id') or self.client_id
276278

277279
# Note: There is no expectation that the client
278280
# specifies the nonce; this is done by the library
@@ -388,17 +390,27 @@ def _random_nonce(self):
388390

389391
async def token_request_from_auth_url(self, method: str, url: str, token_params,
390392
headers, auth_params):
393+
# Extract URL parameters using utility function
394+
clean_url, url_params = extract_url_params(url)
395+
391396
body = None
392397
params = None
393398
if method == 'GET':
394399
body = {}
395-
params = dict(auth_params, **token_params)
400+
# Merge URL params, auth_params, and token_params (later params override earlier ones)
401+
# we do this because httpx version has inconsistency and some versions override query params
402+
# that are specified in url string
403+
params = {**url_params, **auth_params, **token_params}
396404
elif method == 'POST':
397405
if isinstance(auth_params, TokenDetails):
398406
auth_params = auth_params.to_dict()
399-
params = {}
407+
# For POST, URL params go in query string, auth_params and token_params go in body
408+
params = url_params
400409
body = dict(auth_params, **token_params)
401410

411+
# Use clean URL for the request
412+
url = clean_url
413+
402414
from ably.http.http import Response
403415
async with httpx.AsyncClient(http2=True) as client:
404416
resp = await client.request(method=method, url=url, headers=headers, params=params, data=body)
@@ -420,6 +432,6 @@ async def token_request_from_auth_url(self, method: str, url: str, token_params,
420432
token_request = response.text
421433
else:
422434
msg = 'auth_url responded with unacceptable content-type ' + content_type + \
423-
', should be either text/plain, application/jwt or application/json',
435+
', should be either text/plain, application/jwt or application/json',
424436
raise AblyAuthException(msg, 401, 40170)
425437
return token_request

ably/util/helper.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import string
44
import asyncio
55
import time
6-
from typing import Callable
6+
from typing import Callable, Tuple, Dict
7+
from urllib.parse import urlparse, parse_qs
78

89

910
def get_random_id():
@@ -25,6 +26,34 @@ def is_token_error(exception):
2526
return 40140 <= exception.code < 40150
2627

2728

29+
def extract_url_params(url: str) -> Tuple[str, Dict[str, str]]:
30+
"""
31+
Extract URL parameters from a URL and return a clean URL and parameters dict.
32+
33+
Args:
34+
url: The URL to parse
35+
36+
Returns:
37+
Tuple of (clean_url_without_params, url_params_dict)
38+
"""
39+
parsed_url = urlparse(url)
40+
url_params = {}
41+
42+
if parsed_url.query:
43+
# Convert query parameters to a flat dictionary
44+
query_params = parse_qs(parsed_url.query)
45+
for key, values in query_params.items():
46+
# Take the last value if multiple values exist for the same key
47+
url_params[key] = values[-1]
48+
49+
# Reconstruct clean URL without query parameters
50+
clean_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
51+
if parsed_url.fragment:
52+
clean_url += f"#{parsed_url.fragment}"
53+
54+
return clean_url, url_params
55+
56+
2857
class Timer:
2958
def __init__(self, timeout: float, callback: Callable):
3059
self._timeout = timeout

0 commit comments

Comments
 (0)