|
15 | 15 | RequestDraft, |
16 | 16 | RequestQueueHead, |
17 | 17 | RequestQueueStats, |
| 18 | + RequestRegistration, |
18 | 19 | ) |
19 | 20 | from apify_client._models import Request as ClientRequest |
20 | 21 | from crawlee.storage_clients.models import AddRequestsResponse, RequestQueueMetadata |
@@ -120,6 +121,15 @@ def _client_request(request: Request, *, handled_at: datetime | None) -> ClientR |
120 | 121 | ) |
121 | 122 |
|
122 | 123 |
|
| 124 | +def _request_registration(request: Request, *, was_already_handled: bool) -> RequestRegistration: |
| 125 | + """Build an `update_request` response reporting the given pre-update handled state.""" |
| 126 | + return RequestRegistration( |
| 127 | + request_id=unique_key_to_request_id(request.unique_key), |
| 128 | + was_already_present=True, |
| 129 | + was_already_handled=was_already_handled, |
| 130 | + ) |
| 131 | + |
| 132 | + |
123 | 133 | def test_unique_key_to_request_id_length() -> None: |
124 | 134 | unique_key = 'exampleKey123' |
125 | 135 | request_id = unique_key_to_request_id(unique_key, request_id_length=15) |
@@ -431,6 +441,67 @@ async def test_shared_is_finished_true_on_queue_with_no_known_requests() -> None |
431 | 441 | api_client.get_request.assert_not_awaited() |
432 | 442 |
|
433 | 443 |
|
| 444 | +async def test_shared_is_finished_true_after_this_client_marked_request_handled() -> None: |
| 445 | + """A request this client marked handled is trusted from the cache, so `is_finished` needs no per-request read.""" |
| 446 | + client, api_client = _make_shared_client() |
| 447 | + request = Request.from_url('https://example.com/1') |
| 448 | + |
| 449 | + api_client.batch_add_requests = AsyncMock(return_value=_batch_result_all_processed([request])) |
| 450 | + await client.add_batch_of_requests([request]) |
| 451 | + |
| 452 | + # The platform reports the pre-update state, so a first-time handle comes back as not yet handled. |
| 453 | + api_client.update_request = AsyncMock(return_value=_request_registration(request, was_already_handled=False)) |
| 454 | + assert await client.mark_request_as_handled(request) is not None |
| 455 | + |
| 456 | + api_client.list_and_lock_head = AsyncMock(return_value=_empty_locked_head()) |
| 457 | + api_client.get_request = AsyncMock() |
| 458 | + |
| 459 | + assert await client.is_finished() is True |
| 460 | + api_client.get_request.assert_not_awaited() |
| 461 | + |
| 462 | + |
| 463 | +async def test_shared_is_finished_false_after_failed_mark_request_as_handled() -> None: |
| 464 | + """A failed `mark_request_as_handled` leaves the request unconfirmed, so `is_finished` re-checks it.""" |
| 465 | + client, api_client = _make_shared_client() |
| 466 | + request = Request.from_url('https://example.com/1') |
| 467 | + request_id = unique_key_to_request_id(request.unique_key) |
| 468 | + |
| 469 | + api_client.batch_add_requests = AsyncMock(return_value=_batch_result_all_processed([request])) |
| 470 | + await client.add_batch_of_requests([request]) |
| 471 | + |
| 472 | + api_client.update_request = AsyncMock(side_effect=RuntimeError('network down')) |
| 473 | + assert await client.mark_request_as_handled(request) is None |
| 474 | + |
| 475 | + api_client.list_and_lock_head = AsyncMock(return_value=_empty_locked_head()) |
| 476 | + api_client.get_request = AsyncMock(return_value=_client_request(request, handled_at=None)) |
| 477 | + |
| 478 | + assert await client.is_finished() is False |
| 479 | + api_client.get_request.assert_awaited_once_with(request_id) |
| 480 | + |
| 481 | + |
| 482 | +async def test_shared_is_finished_false_after_reclaiming_handled_request() -> None: |
| 483 | + """A reclaimed previously-handled request is pending again, so `is_finished` re-checks it via the platform.""" |
| 484 | + client, api_client = _make_shared_client() |
| 485 | + request = Request.from_url('https://example.com/1') |
| 486 | + request_id = unique_key_to_request_id(request.unique_key) |
| 487 | + |
| 488 | + api_client.batch_add_requests = AsyncMock(return_value=_batch_result_all_processed([request])) |
| 489 | + await client.add_batch_of_requests([request]) |
| 490 | + |
| 491 | + api_client.update_request = AsyncMock(return_value=_request_registration(request, was_already_handled=False)) |
| 492 | + await client.mark_request_as_handled(request) |
| 493 | + |
| 494 | + # Reclaim the handled request: the platform reports the pre-update (handled) state. |
| 495 | + api_client.update_request = AsyncMock(return_value=_request_registration(request, was_already_handled=True)) |
| 496 | + assert await client.reclaim_request(request) is not None |
| 497 | + |
| 498 | + api_client.list_and_lock_head = AsyncMock(return_value=_empty_locked_head()) |
| 499 | + api_client.get_request = AsyncMock(return_value=_client_request(request, handled_at=None)) |
| 500 | + |
| 501 | + assert await client.is_finished() is False |
| 502 | + api_client.get_request.assert_awaited_once_with(request_id) |
| 503 | + |
| 504 | + |
434 | 505 | async def test_shared_is_finished_false_while_add_batch_in_flight() -> None: |
435 | 506 | """The queue does not report finished while an `add_batch_of_requests` call is still in flight.""" |
436 | 507 | client, api_client = _make_shared_client() |
|
0 commit comments