stability and vps runtime updates
This commit is contained in:
26
tests/test_api_tasks.py
Normal file
26
tests/test_api_tasks.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from iaai_scraper.api.routes.tasks import SyncListingRequest, SyncVehicleRequest
|
||||
|
||||
|
||||
def test_sync_vehicle_request_rejects_empty_vehicle_url() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
SyncVehicleRequest(vehicle_url=" ")
|
||||
|
||||
|
||||
def test_sync_vehicle_request_normalizes_whitespace_fields() -> None:
|
||||
body = SyncVehicleRequest(vehicle_url=" https://www.iaai.com/VehicleDetails/123 ", lane=" ")
|
||||
|
||||
assert body.vehicle_url == "https://www.iaai.com/VehicleDetails/123"
|
||||
assert body.lane == "iaai"
|
||||
|
||||
|
||||
def test_sync_listing_request_normalizes_empty_filters_and_lane() -> None:
|
||||
body = SyncListingRequest(make=" ", model="\t", lane=" ")
|
||||
|
||||
assert body.make is None
|
||||
assert body.model is None
|
||||
assert body.lane == "iaai_cars"
|
||||
@@ -314,6 +314,46 @@ class TestScraperSync(unittest.TestCase):
|
||||
self.assertEqual({u for u, _ in fallback_urls}, {urls[1], urls[2]})
|
||||
self.assertNotIn(urls[0], {u for u, _ in fallback_urls})
|
||||
|
||||
def test_sync_listing_marks_failed_on_unfiltered_zero_links_first_page(self) -> None:
|
||||
scraper = self._make_scraper()
|
||||
scraper.persistence.create_tables = MagicMock()
|
||||
scraper.persistence.start_sync_run = MagicMock(return_value=100)
|
||||
scraper.persistence.finish_sync_run = MagicMock()
|
||||
|
||||
scraper._sync_listing_streaming = MagicMock(return_value={
|
||||
"listing": {
|
||||
"status": "ok",
|
||||
"pages_collected": 1,
|
||||
"vehicles_collected": 0,
|
||||
"vehicle_urls": [],
|
||||
"early_stopped": False,
|
||||
"truncated_by_time_budget": False,
|
||||
"pages": [{"page_number": 1, "links_found": 0}],
|
||||
},
|
||||
"total": 0,
|
||||
"skipped_existing": 0,
|
||||
"cars_upserted": 0,
|
||||
"cars_failed": 0,
|
||||
"images_upserted": 0,
|
||||
"protection_events": 0,
|
||||
"failures": [],
|
||||
"all_listing_origin_urls": set(),
|
||||
})
|
||||
|
||||
result = scraper.sync_listing(
|
||||
make=None,
|
||||
model=None,
|
||||
lane="iaai_cars",
|
||||
limit=None,
|
||||
only_new=False,
|
||||
listing_url="https://www.iaai.com/Vehiclelisting/Cars",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "failed")
|
||||
self.assertTrue(result["anti_bot_detected"])
|
||||
self.assertTrue(result["suspected_ip_block"])
|
||||
self.assertTrue(any("IP/proxy block" in f.get("error", "") for f in result["failures"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -10,6 +10,20 @@ from iaai_scraper.core.config import settings as base_settings
|
||||
|
||||
|
||||
class TestWorkerTaskLockHelpers(unittest.TestCase):
|
||||
def test_sync_vehicle_task_skips_empty_vehicle_url(self) -> None:
|
||||
with patch.object(tasks, "_get_persistence") as get_persistence:
|
||||
get_persistence.return_value = MagicMock()
|
||||
|
||||
tasks.sync_vehicle_task.push_request(id="task-empty-vehicle")
|
||||
try:
|
||||
result = tasks.sync_vehicle_task.run(vehicle_url=" ", lane=" ")
|
||||
finally:
|
||||
tasks.sync_vehicle_task.pop_request()
|
||||
|
||||
self.assertEqual(result["status"], "skipped")
|
||||
self.assertEqual(result["reason"], "empty_vehicle_url")
|
||||
get_persistence.assert_not_called()
|
||||
|
||||
def test_lock_acquire_refresh_release(self) -> None:
|
||||
redis_client = MagicMock()
|
||||
|
||||
@@ -228,6 +242,22 @@ class TestWorkerTaskLockHelpers(unittest.TestCase):
|
||||
segments = [{"make": "ACURA"}, {"make": "AUDI"}]
|
||||
with patch.object(tasks, "_get_persistence") as get_persistence, \
|
||||
patch.object(tasks, "_get_redis") as get_redis, \
|
||||
patch.object(
|
||||
tasks,
|
||||
"Settings",
|
||||
return_value=replace(
|
||||
base_settings,
|
||||
discovery=replace(
|
||||
base_settings.discovery,
|
||||
always_full_scan=False,
|
||||
mode="listing",
|
||||
),
|
||||
celery=replace(
|
||||
base_settings.celery,
|
||||
parallel_segments=False,
|
||||
),
|
||||
),
|
||||
), \
|
||||
patch.object(tasks, "_acquire_lock", return_value=True), \
|
||||
patch.object(tasks, "_is_full_scan_done", return_value=True), \
|
||||
patch.object(tasks, "_start_lock_heartbeat") as start_heartbeat, \
|
||||
@@ -257,7 +287,7 @@ class TestWorkerTaskLockHelpers(unittest.TestCase):
|
||||
with patch.object(tasks, "IAAIScraper", return_value=scraper_ctx):
|
||||
tasks.sync_listing_task.push_request(id="task-792")
|
||||
try:
|
||||
result = tasks.sync_listing_task.run()
|
||||
result = tasks.sync_listing_task.run(limit=1)
|
||||
finally:
|
||||
tasks.sync_listing_task.pop_request()
|
||||
|
||||
@@ -420,6 +450,49 @@ class TestWorkerTaskLockHelpers(unittest.TestCase):
|
||||
clear_pending.assert_called()
|
||||
task_app.send_task.assert_not_called()
|
||||
|
||||
def test_sync_listing_task_stops_bootstrap_followup_after_no_progress_limit(self) -> None:
|
||||
with patch.object(tasks, "_get_persistence") as get_persistence, \
|
||||
patch.object(tasks, "_get_redis") as get_redis, \
|
||||
patch.object(tasks, "_acquire_lock", return_value=True), \
|
||||
patch.object(tasks, "_is_full_scan_done", return_value=False), \
|
||||
patch.object(tasks, "_start_lock_heartbeat", return_value=(MagicMock(), MagicMock())), \
|
||||
patch.object(tasks, "_release_lock_if_owner"), \
|
||||
patch.object(tasks, "_run_browser_job", return_value={
|
||||
"run_id": 101,
|
||||
"status": "success",
|
||||
"full_scan_completed": False,
|
||||
"cars_upserted": 0,
|
||||
"cars_failed": 0,
|
||||
"images_upserted": 0,
|
||||
"skipped_existing": 0,
|
||||
"total_discovered": 0,
|
||||
"listing": {"vehicles_collected": 0},
|
||||
"failures": [],
|
||||
}), \
|
||||
patch.object(tasks, "_bump_bootstrap_no_progress_streak", return_value=(
|
||||
tasks.SYNC_LISTING_BOOTSTRAP_NO_PROGRESS_STREAK_LIMIT,
|
||||
False,
|
||||
)), \
|
||||
patch.object(tasks, "_set_full_scan_done") as set_done, \
|
||||
patch.object(tasks, "_clear_sync_checkpoint") as clear_checkpoint, \
|
||||
patch.object(tasks.sync_listing_task, "update_state"):
|
||||
get_persistence.return_value = MagicMock()
|
||||
redis_client = MagicMock()
|
||||
redis_client.get.return_value = None
|
||||
get_redis.return_value = redis_client
|
||||
|
||||
with patch.object(tasks.sync_listing_task, "app", new=MagicMock()) as task_app:
|
||||
tasks.sync_listing_task.push_request(id="task-no-progress")
|
||||
try:
|
||||
result = tasks.sync_listing_task.run()
|
||||
finally:
|
||||
tasks.sync_listing_task.pop_request()
|
||||
|
||||
self.assertEqual(result["status"], "failed")
|
||||
set_done.assert_called()
|
||||
clear_checkpoint.assert_called()
|
||||
task_app.send_task.assert_not_called()
|
||||
|
||||
def test_sync_listing_task_soft_timeout_deduplicates_continuation(self) -> None:
|
||||
with patch.object(tasks, "_get_persistence") as get_persistence, \
|
||||
patch.object(tasks, "_get_redis") as get_redis, \
|
||||
|
||||
Reference in New Issue
Block a user