Skip to content

Commit efb1dc1

Browse files
committed
Consolidate tests
1 parent f00e831 commit efb1dc1

41 files changed

Lines changed: 1826 additions & 4585 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tests/integration/conftest.py

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,73 @@
1+
"""Pytest configuration and fixtures for integration tests.
2+
3+
Provides sync/async client parametrization and shared test fixtures.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import asyncio
19
import json
210
import os
3-
from collections.abc import Generator
11+
import time
12+
from typing import TYPE_CHECKING, Any, TypeVar, overload
413

514
import pytest
615

716
from .utils import DatasetFixture, KvsFixture, get_crypto_random_object_id
817
from apify_client import ApifyClient, ApifyClientAsync
918
from apify_client._utils import create_hmac_signature, create_storage_content_signature
1019

20+
if TYPE_CHECKING:
21+
from collections.abc import Coroutine, Generator
22+
23+
# Environment variable names for test configuration
1124
TOKEN_ENV_VAR = 'APIFY_TEST_USER_API_TOKEN'
1225
TOKEN_ENV_VAR_2 = 'APIFY_TEST_USER_2_API_TOKEN'
1326
API_URL_ENV_VAR = 'APIFY_INTEGRATION_TESTS_API_URL'
1427

28+
T = TypeVar('T')
29+
30+
31+
# ============================================================================
32+
# Helper functions for sync/async test compatibility
33+
# ============================================================================
34+
35+
36+
@overload
37+
async def maybe_await(value: Coroutine[Any, Any, T]) -> T: ...
38+
39+
40+
@overload
41+
async def maybe_await(value: T) -> T: ...
42+
43+
44+
async def maybe_await(value: T | Coroutine[Any, Any, T]) -> T:
45+
"""Await coroutines, pass through other values.
46+
47+
Enables unified test code for both sync and async clients:
48+
result = await maybe_await(client.datasets().list())
49+
"""
50+
if hasattr(value, '__await__'):
51+
return await value # type: ignore[misc,return-value]
52+
return value
53+
54+
55+
async def maybe_sleep(seconds: float, *, is_async: bool) -> None:
56+
"""Sleep using asyncio or time.sleep based on client type."""
57+
if is_async:
58+
await asyncio.sleep(seconds)
59+
else:
60+
time.sleep(seconds) # noqa: ASYNC251
61+
62+
63+
# ============================================================================
64+
# Session-scoped fixtures (created once per test session)
65+
# ============================================================================
66+
1567

1668
@pytest.fixture(scope='session')
1769
def api_token() -> str:
70+
"""Primary test user API token."""
1871
token = os.getenv(TOKEN_ENV_VAR)
1972
if not token:
2073
raise RuntimeError(f'{TOKEN_ENV_VAR} environment variable is missing, cannot run tests!')
@@ -23,44 +76,27 @@ def api_token() -> str:
2376

2477
@pytest.fixture(scope='session')
2578
def api_token_2() -> str:
26-
"""API token for the second test user for storage permission tests."""
79+
"""Secondary test user API token for permission tests."""
2780
token = os.getenv(TOKEN_ENV_VAR_2)
2881
if not token:
2982
raise RuntimeError(f'{TOKEN_ENV_VAR_2} environment variable is missing, cannot run permission tests!')
3083
return token
3184

3285

33-
@pytest.fixture
34-
def apify_client(api_token: str) -> ApifyClient:
35-
return ApifyClient(api_token, api_url=os.getenv(API_URL_ENV_VAR))
36-
37-
38-
# This fixture can't be session-scoped,
39-
# because then you start getting `RuntimeError: Event loop is closed` errors,
40-
# because `impit.AsyncClient` in `ApifyClientAsync` tries to reuse the same event loop across requests,
41-
# but `pytest-asyncio` closes the event loop after each test,
42-
# and uses a new one for the next test.
43-
@pytest.fixture
44-
def apify_client_async(api_token: str) -> ApifyClientAsync:
45-
return ApifyClientAsync(api_token, api_url=os.getenv(API_URL_ENV_VAR))
46-
47-
4886
@pytest.fixture(scope='session')
4987
def test_dataset_of_another_user(api_token_2: str) -> Generator[DatasetFixture]:
50-
"""Pre-existing named dataset of another test user with restricted access."""
88+
"""Dataset owned by secondary user for testing cross-user access restrictions."""
5189
client = ApifyClient(api_token_2, api_url=os.getenv(API_URL_ENV_VAR))
5290

91+
# Create dataset with test data
5392
dataset_name = f'API-test-permissions-{get_crypto_random_object_id()}'
5493
dataset = client.datasets().get_or_create(name=dataset_name)
5594
dataset_client = client.dataset(dataset_id=dataset.id)
5695
expected_content = [{'item1': 1, 'item2': 2, 'item3': 3}, {'item1': 4, 'item2': 5, 'item3': 6}]
57-
58-
# Push data to dataset
5996
dataset_client.push_items(json.dumps(expected_content))
6097

98+
# Generate signature for authenticated access
6199
assert dataset.url_signing_secret_key is not None
62-
63-
# Generate signature for the test
64100
signature = create_storage_content_signature(
65101
resource_id=dataset.id,
66102
url_signing_secret_key=dataset.url_signing_secret_key,
@@ -69,29 +105,29 @@ def test_dataset_of_another_user(api_token_2: str) -> Generator[DatasetFixture]:
69105
yield DatasetFixture(
70106
id=dataset.id,
71107
signature=signature,
72-
expected_content=[{'item1': 1, 'item2': 2, 'item3': 3}, {'item1': 4, 'item2': 5, 'item3': 6}],
108+
expected_content=expected_content,
73109
)
74110

75111
dataset_client.delete()
76112

77113

78114
@pytest.fixture(scope='session')
79115
def test_kvs_of_another_user(api_token_2: str) -> Generator[KvsFixture]:
80-
"""Pre-existing named key value store of another test user with restricted access."""
116+
"""Key-value store owned by secondary user for testing cross-user access restrictions."""
81117
client = ApifyClient(api_token_2, api_url=os.getenv(API_URL_ENV_VAR))
82118

119+
# Create key-value store with test data
83120
kvs_name = f'API-test-permissions-{get_crypto_random_object_id()}'
84121
kvs = client.key_value_stores().get_or_create(name=kvs_name)
85122
kvs_client = client.key_value_store(key_value_store_id=kvs.id)
86123
expected_content = {'key1': 1, 'key2': 2, 'key3': 3}
87-
88-
# Push data to kvs
89124
for key, value in expected_content.items():
90125
kvs_client.set_record(key, value)
91126

92-
# Generate signature for the test
127+
# Generate signatures for authenticated access
93128
signature = create_storage_content_signature(
94-
resource_id=kvs.id, url_signing_secret_key=kvs.url_signing_secret_key or ''
129+
resource_id=kvs.id,
130+
url_signing_secret_key=kvs.url_signing_secret_key or '',
95131
)
96132

97133
yield KvsFixture(
@@ -102,3 +138,42 @@ def test_kvs_of_another_user(api_token_2: str) -> Generator[KvsFixture]:
102138
)
103139

104140
kvs_client.delete()
141+
142+
143+
# ============================================================================
144+
# Function-scoped fixtures (created for each test)
145+
# ============================================================================
146+
147+
148+
@pytest.fixture
149+
def apify_client(api_token: str) -> ApifyClient:
150+
"""Sync Apify client instance."""
151+
return ApifyClient(api_token, api_url=os.getenv(API_URL_ENV_VAR))
152+
153+
154+
@pytest.fixture
155+
def apify_client_async(api_token: str) -> ApifyClientAsync:
156+
"""Async Apify client instance."""
157+
return ApifyClientAsync(api_token, api_url=os.getenv(API_URL_ENV_VAR))
158+
159+
160+
@pytest.fixture(params=['sync', 'async'])
161+
def client_type(request: pytest.FixtureRequest) -> str:
162+
"""Parametrize tests to run with both sync and async clients."""
163+
return request.param
164+
165+
166+
@pytest.fixture
167+
def client(
168+
client_type: str,
169+
apify_client: ApifyClient,
170+
apify_client_async: ApifyClientAsync,
171+
) -> ApifyClient | ApifyClientAsync:
172+
"""Return sync or async client based on parametrization."""
173+
return apify_client if client_type == 'sync' else apify_client_async
174+
175+
176+
@pytest.fixture
177+
def is_async(client_type: str) -> bool:
178+
"""True if current test is using async client."""
179+
return client_type == 'async'

0 commit comments

Comments
 (0)