diff --git a/mobilede_scraper/mobile_de/client.py b/mobilede_scraper/mobile_de/client.py index 941dfb3..f0e022f 100644 --- a/mobilede_scraper/mobile_de/client.py +++ b/mobilede_scraper/mobile_de/client.py @@ -454,7 +454,7 @@ class MobileDeClient: ) or None return MobileDeListing( id=listing_id, - url=MobileDeClient.build_detail_url(listing_id) if listing_id else "", + url=MobileDeClient.build_transport_detail_url(listing_id) if listing_id else "", title=item.get("shortTitle"), subtitle=item.get("subTitle"), price=item.get("p"), diff --git a/mobilede_scraper/mobile_de/mapper.py b/mobilede_scraper/mobile_de/mapper.py index 939e186..ac47633 100644 --- a/mobilede_scraper/mobile_de/mapper.py +++ b/mobilede_scraper/mobile_de/mapper.py @@ -340,7 +340,7 @@ class MobileDeMapper: new_car=self._bool_value(detail.get("isConditionNew")), is_hidden=False, origin="MOBILEDE", - origin_url=MobileDeClient.build_detail_url(listing_id), + origin_url=MobileDeClient.build_transport_detail_url(listing_id), origin_id=origin_id, is_damaged=is_damaged, evaluation=self._rating_text(detail.get("priceRating") or detail.get("rating")), diff --git a/mobilede_scraper/storage/db.py b/mobilede_scraper/storage/db.py index 9bf000f..c108530 100644 --- a/mobilede_scraper/storage/db.py +++ b/mobilede_scraper/storage/db.py @@ -841,7 +841,7 @@ class PersistenceService: order_by = Car.last_seen_at.desc() if newest_first else Car.last_seen_at.asc() with self.session_scope() as session: result = session.execute( - select(Car.id, Car.origin_url, Car.last_seen_at).where( + select(Car.id, Car.origin_id, Car.last_seen_at).where( _origin_prefix_filter(Car.origin_id, prefixes), Car.is_sold == False, # noqa: E712 ).order_by(order_by).limit(limit) diff --git a/mobilede_scraper/worker/tasks.py b/mobilede_scraper/worker/tasks.py index a242169..2c160f7 100644 --- a/mobilede_scraper/worker/tasks.py +++ b/mobilede_scraper/worker/tasks.py @@ -1571,12 +1571,14 @@ def _mobilede_probe_active_listing_status( client: MobileDeClient, *, car_id: int, - origin_url: str, + origin_id: str, ) -> tuple[str, int | None]: - url = str(origin_url or "").strip() - if not url: - logger.warning("mobile.de sold probe skipped: car_id=%s reason=missing_origin_url", car_id) + source_id = str(origin_id or "").strip() + listing_id = source_id.rsplit(":", 1)[-1].strip() + if not listing_id: + logger.warning("mobile.de sold probe skipped: car_id=%s reason=missing_origin_id", car_id) return "skipped", None + url = MobileDeClient.build_transport_detail_url(listing_id) try: client.fetch_html(url, timeout=30) return "available", 200 @@ -2270,11 +2272,11 @@ def mobilede_verify_active_sold_batch_task( skipped = 0 checked = 0 - for index, (car_id, origin_url, _last_seen_at) in enumerate(active_cars): + for index, (car_id, origin_id, _last_seen_at) in enumerate(active_cars): verdict, status_code = _mobilede_probe_active_listing_status( client, car_id=int(car_id), - origin_url=str(origin_url or ""), + origin_id=str(origin_id or ""), ) checked += 1 if verdict == "available": diff --git a/tests/test_mobilede_detail_transports.py b/tests/test_mobilede_detail_transports.py index 7dd48f3..58ecf67 100644 --- a/tests/test_mobilede_detail_transports.py +++ b/tests/test_mobilede_detail_transports.py @@ -126,6 +126,18 @@ class TestMobileDeDetailTransports(unittest.TestCase): self.assertEqual(record.year, 2022) self.assertEqual(record.mileage, 18285) self.assertEqual(len(record.images), 1) + self.assertEqual( + record.origin_url, + MobileDeClient.build_transport_detail_url("461936419"), + ) + + def test_search_listing_uses_russian_transport_detail_url(self) -> None: + listing = MobileDeClient._map_listing({"id": "461936419"}) + + self.assertEqual( + listing.url, + MobileDeClient.build_transport_detail_url("461936419"), + ) def test_legacy_transport_is_explicitly_opt_in(self) -> None: client = MobileDeClient() diff --git a/tests/test_worker_runtime_tasks.py b/tests/test_worker_runtime_tasks.py index b4e364e..773ff3b 100644 --- a/tests/test_worker_runtime_tasks.py +++ b/tests/test_worker_runtime_tasks.py @@ -277,7 +277,7 @@ class TestWorkerRuntimeTaskHelpers(unittest.TestCase): redis_client = self._FakeRedis() persistence = MagicMock() persistence.get_active_cars_batch_for_sold_probe.return_value = [ - (index, f"https://example.test/{index}", datetime.now(timezone.utc)) + (index, f"mobile.de:{index}", datetime.now(timezone.utc)) for index in range(1, 6) ] client = MagicMock() @@ -300,9 +300,28 @@ class TestWorkerRuntimeTaskHelpers(unittest.TestCase): self.assertEqual(result["blocked"], 3) self.assertEqual(result["skipped"], 2) self.assertEqual(probe.call_count, 3) + self.assertEqual( + [call.kwargs["origin_id"] for call in probe.call_args_list], + ["mobile.de:1", "mobile.de:2", "mobile.de:3"], + ) persistence.mark_cars_sold_by_ids.assert_not_called() self.assertIn(tasks.MOBILEDE_SOLD_PROBE_ANTIBOT_BLOCK_KEY, redis_client.store) + def test_sold_probe_uses_russian_transport_url_from_origin_id(self) -> None: + client = MagicMock() + + verdict, status_code = tasks._mobilede_probe_active_listing_status( + client, + car_id=1, + origin_id="mobile.de:461936419", + ) + + self.assertEqual((verdict, status_code), ("available", 200)) + client.fetch_html.assert_called_once_with( + "https://www.mobile.de/ru/транспортные-средства/подробности.html?id=461936419&vc=Car&s=Car&lang=ru", + timeout=30, + ) + def test_runtime_numeric_filters_intersect_planner_segment(self) -> None: self.assertEqual( search_sync._intersect_numeric_bounds("10000", "30000", 15000, 25000),