Skip to content

Commit da43107

Browse files
committed
test: Fix flaky *_collection_iterate tests with polling helper
1 parent 1f92625 commit da43107

7 files changed

Lines changed: 122 additions & 87 deletions

File tree

tests/integration/_utils.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import secrets
55
import string
66
import time
7+
from collections.abc import AsyncIterator, Iterator
78
from dataclasses import dataclass
8-
from typing import TYPE_CHECKING, Any, TypeVar, overload
9+
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload
910

1011
import pytest
1112

1213
if TYPE_CHECKING:
13-
from collections.abc import Coroutine
14+
from collections.abc import Callable, Coroutine
1415

1516
# Environment variable names for test configuration
1617
TOKEN_ENV_VAR = 'APIFY_TEST_USER_API_TOKEN'
@@ -20,6 +21,16 @@
2021
T = TypeVar('T')
2122

2223

24+
class _HasId(Protocol):
25+
"""Items returned by collection `iterate()` endpoints all expose `.id`."""
26+
27+
@property
28+
def id(self) -> str: ...
29+
30+
31+
_HasIdT = TypeVar('_HasIdT', bound=_HasId)
32+
33+
2334
# ============================================================================
2435
# Data classes for test fixtures
2536
# ============================================================================
@@ -108,6 +119,55 @@ async def maybe_sleep(seconds: float, *, is_async: bool) -> None:
108119
time.sleep(seconds) # noqa: ASYNC251
109120

110121

122+
async def collect_iterate_until_present(
123+
iterator_factory: Callable[[], Iterator[_HasIdT] | AsyncIterator[_HasIdT]],
124+
expected_ids: set[str],
125+
*,
126+
item_type: type[_HasIdT],
127+
is_async: bool,
128+
max_attempts: int = 5,
129+
interval: float = 1.0,
130+
) -> list[_HasIdT]:
131+
"""Drain a collection `iterate()` until every expected ID is present.
132+
133+
Handles eventual consistency on listing endpoints: under parallel load a freshly
134+
created resource may not appear in the listing for a short window. Each attempt
135+
builds a fresh iterator via `iterator_factory`, drains it, and breaks early once
136+
`expected_ids` is a subset of the collected items' `.id` values. The most recent
137+
collection is returned regardless of whether the condition was met, so the caller
138+
can run its own assertion with a helpful failure message.
139+
140+
Args:
141+
iterator_factory: No-arg callable returning a fresh iterator on each call.
142+
expected_ids: IDs that must all appear in the collected items.
143+
item_type: Asserted to match the runtime type of each yielded item.
144+
is_async: Whether the iterator is async (and so are sleeps).
145+
max_attempts: Maximum number of polling rounds.
146+
interval: Seconds to sleep before each attempt.
147+
148+
Returns:
149+
The most recently collected items.
150+
"""
151+
collected: list[_HasIdT] = []
152+
for _ in range(max_attempts):
153+
await maybe_sleep(interval, is_async=is_async)
154+
iterator = iterator_factory()
155+
collected = []
156+
if is_async:
157+
assert isinstance(iterator, AsyncIterator)
158+
async for item in iterator:
159+
assert isinstance(item, item_type)
160+
collected.append(item)
161+
else:
162+
assert isinstance(iterator, Iterator)
163+
for item in iterator:
164+
assert isinstance(item, item_type)
165+
collected.append(item)
166+
if expected_ids.issubset(item.id for item in collected):
167+
break
168+
return collected
169+
170+
111171
# ============================================================================
112172
# Pytest markers and parametrization
113173
# ============================================================================

tests/integration/test_dataset.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
import impit
1212
import pytest
1313

14-
from ._utils import DatasetFixture, get_random_resource_name, maybe_await, maybe_sleep
14+
from ._utils import (
15+
DatasetFixture,
16+
collect_iterate_until_present,
17+
get_random_resource_name,
18+
maybe_await,
19+
maybe_sleep,
20+
)
1521
from apify_client._models import Dataset, DatasetListItem, DatasetStatistics, ListOfDatasets
1622
from apify_client._resource_clients.dataset import DatasetItemsPage
1723
from apify_client.errors import ApifyApiError
@@ -432,19 +438,12 @@ async def test_dataset_collection_iterate(client: ApifyClient | ApifyClientAsync
432438
created_ids.append(dataset.id)
433439

434440
try:
435-
iterator = client.datasets().iterate(desc=True)
436-
collected: list[DatasetListItem] = []
437-
if is_async:
438-
assert isinstance(iterator, AsyncIterator)
439-
async for ds in iterator:
440-
assert isinstance(ds, DatasetListItem)
441-
collected.append(ds)
442-
else:
443-
assert isinstance(iterator, Iterator)
444-
for ds in iterator:
445-
assert isinstance(ds, DatasetListItem)
446-
collected.append(ds)
447-
441+
collected = await collect_iterate_until_present(
442+
lambda: client.datasets().iterate(desc=True),
443+
set(created_ids),
444+
item_type=DatasetListItem,
445+
is_async=is_async,
446+
)
448447
collected_ids = {ds.id for ds in collected}
449448
for created_id in created_ids:
450449
assert created_id in collected_ids

tests/integration/test_key_value_store.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
import impit
1111
import pytest
1212

13-
from ._utils import KvsFixture, get_random_resource_name, maybe_await, maybe_sleep
13+
from ._utils import (
14+
KvsFixture,
15+
collect_iterate_until_present,
16+
get_random_resource_name,
17+
maybe_await,
18+
maybe_sleep,
19+
)
1420
from apify_client._models import KeyValueStore, KeyValueStoreKey, ListOfKeys, ListOfKeyValueStores
1521
from apify_client.errors import ApifyApiError
1622

@@ -555,19 +561,12 @@ async def test_key_value_store_collection_iterate(client: ApifyClient | ApifyCli
555561
created_ids.append(kvs.id)
556562

557563
try:
558-
iterator = client.key_value_stores().iterate(desc=True)
559-
collected: list[KeyValueStore] = []
560-
if is_async:
561-
assert isinstance(iterator, AsyncIterator)
562-
async for kvs in iterator:
563-
assert isinstance(kvs, KeyValueStore)
564-
collected.append(kvs)
565-
else:
566-
assert isinstance(iterator, Iterator)
567-
for kvs in iterator:
568-
assert isinstance(kvs, KeyValueStore)
569-
collected.append(kvs)
570-
564+
collected = await collect_iterate_until_present(
565+
lambda: client.key_value_stores().iterate(desc=True),
566+
set(created_ids),
567+
item_type=KeyValueStore,
568+
is_async=is_async,
569+
)
571570
collected_ids = {kvs.id for kvs in collected}
572571
for created_id in created_ids:
573572
assert created_id in collected_ids

tests/integration/test_request_queue.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
from datetime import timedelta
77
from typing import TYPE_CHECKING
88

9-
from ._utils import get_random_resource_name, get_random_string, maybe_await, maybe_sleep
9+
from ._utils import (
10+
collect_iterate_until_present,
11+
get_random_resource_name,
12+
get_random_string,
13+
maybe_await,
14+
maybe_sleep,
15+
)
1016
from apify_client._models import (
1117
BatchAddResult,
1218
BatchDeleteResult,
@@ -615,19 +621,12 @@ async def test_request_queue_collection_iterate(client: ApifyClient | ApifyClien
615621
created_ids.append(rq.id)
616622

617623
try:
618-
iterator = client.request_queues().iterate(desc=True)
619-
collected: list[RequestQueueShort] = []
620-
if is_async:
621-
assert isinstance(iterator, AsyncIterator)
622-
async for rq in iterator:
623-
assert isinstance(rq, RequestQueueShort)
624-
collected.append(rq)
625-
else:
626-
assert isinstance(iterator, Iterator)
627-
for rq in iterator:
628-
assert isinstance(rq, RequestQueueShort)
629-
collected.append(rq)
630-
624+
collected = await collect_iterate_until_present(
625+
lambda: client.request_queues().iterate(desc=True),
626+
set(created_ids),
627+
item_type=RequestQueueShort,
628+
is_async=is_async,
629+
)
631630
collected_ids = {rq.id for rq in collected}
632631
for rq_id in created_ids:
633632
assert rq_id in collected_ids

tests/integration/test_schedule.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
from __future__ import annotations
44

5-
from collections.abc import AsyncIterator, Iterator
65
from typing import TYPE_CHECKING
76

8-
from ._utils import get_random_resource_name, maybe_await
7+
from ._utils import collect_iterate_until_present, get_random_resource_name, maybe_await
98
from apify_client._models import Actor, ListOfSchedules, Schedule, ScheduleActionRunActor, ScheduleShort
109

1110
if TYPE_CHECKING:
@@ -193,19 +192,12 @@ async def test_schedule_collection_iterate(client: ApifyClient | ApifyClientAsyn
193192
created_ids.append(schedule.id)
194193

195194
try:
196-
iterator = client.schedules().iterate()
197-
collected: list[ScheduleShort] = []
198-
if is_async:
199-
assert isinstance(iterator, AsyncIterator)
200-
async for s in iterator:
201-
assert isinstance(s, ScheduleShort)
202-
collected.append(s)
203-
else:
204-
assert isinstance(iterator, Iterator)
205-
for s in iterator:
206-
assert isinstance(s, ScheduleShort)
207-
collected.append(s)
208-
195+
collected = await collect_iterate_until_present(
196+
lambda: client.schedules().iterate(),
197+
set(created_ids),
198+
item_type=ScheduleShort,
199+
is_async=is_async,
200+
)
209201
collected_ids = {s.id for s in collected}
210202
for sched_id in created_ids:
211203
assert sched_id in collected_ids

tests/integration/test_task.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from datetime import timedelta
77
from typing import TYPE_CHECKING
88

9-
from ._utils import get_random_resource_name, maybe_await
9+
from ._utils import collect_iterate_until_present, get_random_resource_name, maybe_await
1010
from apify_client._models import Actor, ListOfRuns, ListOfTasks, ListOfWebhooks, Run, RunShort, Task, TaskShort
1111

1212
if TYPE_CHECKING:
@@ -365,19 +365,12 @@ async def test_task_collection_iterate(client: ApifyClient | ApifyClientAsync, *
365365
created_ids.append(task.id)
366366

367367
try:
368-
iterator = client.tasks().iterate(desc=True)
369-
collected: list[TaskShort] = []
370-
if is_async:
371-
assert isinstance(iterator, AsyncIterator)
372-
async for t in iterator:
373-
assert isinstance(t, TaskShort)
374-
collected.append(t)
375-
else:
376-
assert isinstance(iterator, Iterator)
377-
for t in iterator:
378-
assert isinstance(t, TaskShort)
379-
collected.append(t)
380-
368+
collected = await collect_iterate_until_present(
369+
lambda: client.tasks().iterate(desc=True),
370+
set(created_ids),
371+
item_type=TaskShort,
372+
is_async=is_async,
373+
)
381374
collected_ids = {t.id for t in collected}
382375
for task_id in created_ids:
383376
assert task_id in collected_ids

tests/integration/test_webhook.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from apify_client import ApifyClient, ApifyClientAsync
1010

1111

12-
from ._utils import maybe_await
12+
from ._utils import collect_iterate_until_present, maybe_await
1313
from apify_client._models import (
1414
ListOfRuns,
1515
ListOfWebhookDispatches,
@@ -219,19 +219,12 @@ async def test_webhook_collection_iterate(client: ApifyClient | ApifyClientAsync
219219
assert len(set(created_ids)) == 3
220220

221221
try:
222-
iterator = client.webhooks().iterate(desc=True)
223-
collected: list[WebhookShort] = []
224-
if is_async:
225-
assert isinstance(iterator, AsyncIterator)
226-
async for w in iterator:
227-
assert isinstance(w, WebhookShort)
228-
collected.append(w)
229-
else:
230-
assert isinstance(iterator, Iterator)
231-
for w in iterator:
232-
assert isinstance(w, WebhookShort)
233-
collected.append(w)
234-
222+
collected = await collect_iterate_until_present(
223+
lambda: client.webhooks().iterate(desc=True),
224+
set(created_ids),
225+
item_type=WebhookShort,
226+
is_async=is_async,
227+
)
235228
collected_ids = {w.id for w in collected}
236229
for webhook_id in created_ids:
237230
assert webhook_id in collected_ids

0 commit comments

Comments
 (0)