11from __future__ import annotations
22
33from datetime import UTC , datetime
4+ from typing import TYPE_CHECKING
45from unittest .mock import AsyncMock
56
67import pytest
78
89from apify_client ._models import Request as ClientRequest
9- from apify_client ._models import RequestQueueHead
10+ from apify_client ._models import RequestQueueHead , RequestRegistration
1011from crawlee .storage_clients .models import RequestQueueMetadata
1112
13+ from apify import Request
14+ from apify .storage_clients ._apify ._request_queue_shared_client import ApifyRequestQueueSharedClient
1215from apify .storage_clients ._apify ._request_queue_single_client import ApifyRequestQueueSingleClient
1316from apify .storage_clients ._apify ._utils import unique_key_to_request_id
1417
18+ if TYPE_CHECKING :
19+ from collections .abc import Callable
1520
16- def _make_single_client (
17- api_client : AsyncMock | None = None ,
18- ) -> tuple [ApifyRequestQueueSingleClient , AsyncMock ]:
19- if api_client is None :
20- api_client = AsyncMock ()
21+
22+ def _make_metadata () -> RequestQueueMetadata :
2123 now = datetime .now (tz = UTC )
22- metadata = RequestQueueMetadata (
24+ return RequestQueueMetadata (
2325 id = 'test-rq-id' ,
2426 name = 'test-rq' ,
2527 accessed_at = now ,
@@ -30,7 +32,28 @@ def _make_single_client(
3032 pending_request_count = 0 ,
3133 total_request_count = 0 ,
3234 )
33- client = ApifyRequestQueueSingleClient (api_client = api_client , metadata = metadata , cache_size = 100 )
35+
36+
37+ def _make_single_client (
38+ api_client : AsyncMock | None = None ,
39+ ) -> tuple [ApifyRequestQueueSingleClient , AsyncMock ]:
40+ if api_client is None :
41+ api_client = AsyncMock ()
42+ client = ApifyRequestQueueSingleClient (api_client = api_client , metadata = _make_metadata (), cache_size = 100 )
43+ return client , api_client
44+
45+
46+ def _make_shared_client (
47+ api_client : AsyncMock | None = None ,
48+ ) -> tuple [ApifyRequestQueueSharedClient , AsyncMock ]:
49+ if api_client is None :
50+ api_client = AsyncMock ()
51+ client = ApifyRequestQueueSharedClient (
52+ api_client = api_client ,
53+ metadata = _make_metadata (),
54+ cache_size = 100 ,
55+ metadata_getter = AsyncMock (),
56+ )
3457 return client , api_client
3558
3659
@@ -136,3 +159,34 @@ async def test_fetch_next_request_skips_already_handled() -> None:
136159 assert result is None , 'Already-handled request must not be fetched.'
137160 assert request_id not in client ._requests_in_progress , 'Handled request must not be left in progress.'
138161 assert request_id in client ._requests_already_handled , 'Handled request id should be cached for deduplication.'
162+
163+
164+ @pytest .mark .parametrize (
165+ 'make_client' ,
166+ [_make_single_client , _make_shared_client ],
167+ ids = ['single_client' , 'shared_client' ],
168+ )
169+ async def test_reclaim_previously_handled_adjusts_counts (
170+ make_client : Callable [[], tuple [ApifyRequestQueueSingleClient | ApifyRequestQueueSharedClient , AsyncMock ]],
171+ ) -> None :
172+ """Reclaiming a previously handled request must move it from handled back to pending in the metadata."""
173+ client , api_client = make_client ()
174+ client .metadata .handled_request_count = 1
175+ client .metadata .pending_request_count = 0
176+
177+ unique_key = 'https://example.com'
178+ request_id = unique_key_to_request_id (unique_key )
179+ request = Request .from_url (unique_key , unique_key = unique_key )
180+ request .handled_at = datetime .now (tz = UTC )
181+
182+ # After reclaiming, the platform reports the request as no longer handled.
183+ api_client .update_request = AsyncMock (
184+ return_value = RequestRegistration .model_validate (
185+ {'requestId' : request_id , 'wasAlreadyPresent' : True , 'wasAlreadyHandled' : False }
186+ )
187+ )
188+
189+ await client .reclaim_request (request )
190+
191+ assert client .metadata .handled_request_count == 0 , 'Reclaimed request must be removed from the handled count.'
192+ assert client .metadata .pending_request_count == 1 , 'Reclaimed request must be added back to the pending count.'
0 commit comments