-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathtest_apify_request_queue_client.py
More file actions
175 lines (146 loc) · 6.05 KB
/
Copy pathtest_apify_request_queue_client.py
File metadata and controls
175 lines (146 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
from __future__ import annotations
from datetime import UTC, datetime
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock
import pytest
from apify_client._models import RequestQueueHead, RequestQueueStats, RequestRegistration
from crawlee.storage_clients.models import RequestQueueMetadata
from apify import Request
from apify.storage_clients._apify._models import ApifyRequestQueueMetadata
from apify.storage_clients._apify._request_queue_shared_client import ApifyRequestQueueSharedClient
from apify.storage_clients._apify._request_queue_single_client import ApifyRequestQueueSingleClient
from apify.storage_clients._apify._utils import unique_key_to_request_id
if TYPE_CHECKING:
from collections.abc import Callable
def _make_metadata() -> RequestQueueMetadata:
now = datetime.now(tz=UTC)
return RequestQueueMetadata(
id='test-rq-id',
name='test-rq',
accessed_at=now,
created_at=now,
modified_at=now,
had_multiple_clients=False,
handled_request_count=0,
pending_request_count=0,
total_request_count=0,
)
def _make_single_client(
api_client: AsyncMock | None = None,
) -> tuple[ApifyRequestQueueSingleClient, AsyncMock]:
if api_client is None:
api_client = AsyncMock()
client = ApifyRequestQueueSingleClient(api_client=api_client, metadata=_make_metadata(), cache_size=100)
return client, api_client
def _make_shared_client(
api_client: AsyncMock | None = None,
) -> tuple[ApifyRequestQueueSharedClient, AsyncMock]:
if api_client is None:
api_client = AsyncMock()
client = ApifyRequestQueueSharedClient(
api_client=api_client,
metadata=_make_metadata(),
cache_size=100,
metadata_getter=AsyncMock(),
)
return client, api_client
def test_unique_key_to_request_id_length() -> None:
unique_key = 'exampleKey123'
request_id = unique_key_to_request_id(unique_key, request_id_length=15)
assert len(request_id) == 15, 'Request ID should have the correct length.'
def test_unique_key_to_request_id_consistency() -> None:
unique_key = 'consistentKey'
request_id_1 = unique_key_to_request_id(unique_key)
request_id_2 = unique_key_to_request_id(unique_key)
assert request_id_1 == request_id_2, 'The same unique key should generate consistent request IDs.'
@pytest.mark.parametrize(
('stats', 'expected_read_count'),
[(None, None), ({'readCount': 5}, 5)],
ids=['none_coerced_to_default', 'populated_passed_through'],
)
def test_metadata_stats_validation(stats: dict | None, expected_read_count: int | None) -> None:
"""A `stats: None` payload (as `open()` produces via `model_dump`) defaults; a populated one passes through."""
now = datetime.now(tz=UTC)
metadata = ApifyRequestQueueMetadata.model_validate(
{
'id': 'test-rq-id',
'name': None,
'accessedAt': now,
'createdAt': now,
'modifiedAt': now,
'hadMultipleClients': False,
'handledRequestCount': 0,
'pendingRequestCount': 0,
'totalRequestCount': 0,
'stats': stats,
}
)
assert isinstance(metadata.stats, RequestQueueStats)
assert metadata.stats.read_count == expected_read_count
@pytest.mark.parametrize(
('unique_key', 'expected_request_id'),
[
('abc', 'ungWv48BzpBQUDe'),
('uniqueKey', 'xiWPs083cree7mH'),
('', '47DEQpj8HBSaTIm'),
('测试中文', 'lKPdJkdvw8MXEUp'),
('test+/=', 'XZRQjhoG0yjfnYD'),
],
ids=[
'basic_abc',
'keyword_uniqueKey',
'empty_string',
'non_ascii_characters',
'url_unsafe_characters',
],
)
def test_unique_key_to_request_id_matches_known_values(unique_key: str, expected_request_id: str) -> None:
request_id = unique_key_to_request_id(unique_key)
assert request_id == expected_request_id, f'Unique key "{unique_key}" should produce the expected request ID.'
@pytest.mark.parametrize(
('in_progress_count', 'expected_limit'),
[
(0, 200),
(300, 500),
(900, ApifyRequestQueueSingleClient._MAX_HEAD_ITEMS),
],
ids=['no_in_progress', 'pads_by_in_progress', 'caps_at_max_head_items'],
)
async def test_list_head_limit(in_progress_count: int, expected_limit: int) -> None:
client, api_client = _make_single_client()
api_client.list_head = AsyncMock(
return_value=RequestQueueHead(
limit=expected_limit,
queue_modified_at=datetime.now(tz=UTC),
had_multiple_clients=False,
items=[],
)
)
client._requests_in_progress = {f'req_{i}' for i in range(in_progress_count)}
await client._list_head()
api_client.list_head.assert_awaited_once_with(limit=expected_limit)
@pytest.mark.parametrize(
'make_client',
[_make_single_client, _make_shared_client],
ids=['single_client', 'shared_client'],
)
async def test_reclaim_previously_handled_adjusts_counts(
make_client: Callable[[], tuple[ApifyRequestQueueSingleClient | ApifyRequestQueueSharedClient, AsyncMock]],
) -> None:
"""Reclaiming a previously handled request must move it from handled back to pending in the metadata."""
client, api_client = make_client()
client.metadata.handled_request_count = 1
client.metadata.pending_request_count = 0
unique_key = 'https://example.com'
request_id = unique_key_to_request_id(unique_key)
request = Request.from_url(unique_key, unique_key=unique_key)
request.handled_at = datetime.now(tz=UTC)
# After reclaiming, the platform reports the request as no longer handled.
api_client.update_request = AsyncMock(
return_value=RequestRegistration.model_validate(
{'requestId': request_id, 'wasAlreadyPresent': True, 'wasAlreadyHandled': False}
)
)
await client.reclaim_request(request)
assert client.metadata.handled_request_count == 0, 'Reclaimed request must be removed from the handled count.'
assert client.metadata.pending_request_count == 1, 'Reclaimed request must be added back to the pending count.'