Skip to content

Commit 3a29e71

Browse files
committed
fix: improve flaky test resilience for channel type and transient errors
- Broaden _is_transient_error to catch 429, rate limits, and resource limit errors (e.g. "maximum number of custom channel types") - Use str(exc) instead of http_response.text for reliable phrase matching - Add @cleanup_channel_types decorator that deletes stale (>2min) non-builtin channel types before tests to free slots in shared CI env - Apply @retry_on_transient_error and @cleanup_channel_types to channel type CRUD tests and event_hooks test
1 parent 7c4c123 commit 3a29e71

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

tests/base.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import datetime
12
import functools
3+
import inspect
24
import time
35
from abc import ABC
46

@@ -52,14 +54,19 @@ def _is_transient_error(exc: Exception) -> bool:
5254
if isinstance(exc, (httpx.ReadTimeout, httpx.ConnectTimeout, httpx.ConnectError)):
5355
return True
5456
if isinstance(exc, StreamAPIException):
55-
body = ""
56-
try:
57-
body = exc.http_response.text or ""
58-
except Exception:
59-
pass
60-
if "upstream connect error" in body or "disconnect" in body:
57+
if exc.status_code in (429, 502, 503, 504):
6158
return True
62-
if exc.status_code in (502, 503, 504):
59+
msg = str(exc).lower()
60+
if any(
61+
phrase in msg
62+
for phrase in (
63+
"upstream connect error",
64+
"disconnect",
65+
"maximum number of",
66+
"rate limit",
67+
"too many",
68+
)
69+
):
6370
return True
6471
return False
6572

@@ -85,3 +92,44 @@ def wrapper(*args, **kwargs):
8592
return wrapper
8693

8794
return decorator
95+
96+
97+
_BUILTIN_CHANNEL_TYPES = frozenset(
98+
{"messaging", "livestream", "team", "gaming", "commerce"}
99+
)
100+
_STALE_THRESHOLD = datetime.timedelta(minutes=2)
101+
102+
103+
def cleanup_channel_types(func):
104+
"""Decorator that deletes stale test channel types before the test runs.
105+
106+
Frees slots toward the 50-type limit without interfering with parallel
107+
runners (only removes types older than 2 minutes). Expects a ``client``
108+
pytest fixture parameter.
109+
"""
110+
111+
@functools.wraps(func)
112+
def wrapper(*args, **kwargs):
113+
# Resolve 'client' from pytest kwargs or positional args
114+
client = kwargs.get("client")
115+
if client is None:
116+
sig = inspect.signature(func)
117+
params = list(sig.parameters)
118+
idx = params.index("client") if "client" in params else 0
119+
client = args[idx]
120+
121+
now = datetime.datetime.now(datetime.timezone.utc)
122+
resp = client.chat.list_channel_types()
123+
for name, config in resp.data.channel_types.items():
124+
if name in _BUILTIN_CHANNEL_TYPES:
125+
continue
126+
if config and (now - config.created_at) > _STALE_THRESHOLD:
127+
try:
128+
client.chat.delete_channel_type(name=name)
129+
except Exception:
130+
pass
131+
time.sleep(2)
132+
133+
return func(*args, **kwargs)
134+
135+
return wrapper

tests/test_chat_misc.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
QueryFutureChannelBansPayload,
1818
SortParamRequest,
1919
)
20+
from tests.base import cleanup_channel_types, retry_on_transient_error
2021

2122

2223
def test_get_app_settings(client: Stream):
@@ -389,6 +390,8 @@ def test_query_future_channel_bans(client: Stream, random_users):
389390
pass
390391

391392

393+
@retry_on_transient_error()
394+
@cleanup_channel_types
392395
def test_create_channel_type(client: Stream):
393396
"""Create a channel type with custom settings."""
394397
type_name = f"testtype{uuid.uuid4().hex[:8]}"
@@ -413,6 +416,8 @@ def test_create_channel_type(client: Stream):
413416
pass
414417

415418

419+
@retry_on_transient_error()
420+
@cleanup_channel_types
416421
def test_update_channel_type_mark_messages_pending(client: Stream):
417422
"""Update a channel type with mark_messages_pending=True."""
418423
type_name = f"testtype{uuid.uuid4().hex[:8]}"
@@ -445,6 +450,8 @@ def test_update_channel_type_mark_messages_pending(client: Stream):
445450
pass
446451

447452

453+
@retry_on_transient_error()
454+
@cleanup_channel_types
448455
def test_update_channel_type_push_notifications(client: Stream):
449456
"""Update a channel type with push_notifications=False."""
450457
type_name = f"testtype{uuid.uuid4().hex[:8]}"
@@ -477,6 +484,8 @@ def test_update_channel_type_push_notifications(client: Stream):
477484
pass
478485

479486

487+
@retry_on_transient_error()
488+
@cleanup_channel_types
480489
def test_delete_channel_type(client: Stream):
481490
"""Create and delete a channel type with retry."""
482491
type_name = f"testdeltype{uuid.uuid4().hex[:8]}"
@@ -540,6 +549,7 @@ def test_get_rate_limits_specific_endpoints(client: Stream):
540549
assert info.remaining >= 0
541550

542551

552+
@retry_on_transient_error()
543553
def test_event_hooks_sqs_sns(client: Stream):
544554
"""Test setting SQS, SNS, and pending_message event hooks."""
545555
# Save original hooks to restore later

0 commit comments

Comments
 (0)