From 3f64d40f7678815219e2b57ed125ffce54fd7c93 Mon Sep 17 00:00:00 2001 From: subzeroid <143403577+subzeroid@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:23:03 +0300 Subject: [PATCH] fix(desktop): keep impossible coordinates and radii off the lookup wire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A latitude outside ±90, a longitude outside ±180 or a radius beyond half the Earth's circumference cannot describe a real place; the desktop host already refuses them, so the core now drops such a post's geotag and reports no radius instead of handing the app a value it will reject after the user has paid. Also pins the documented worst case of lookup.activity — six pages, each retried once, twelve requests — with one test. --- insto/desktop/lookup.py | 23 +++++++++++++--- tests/test_desktop_lookup.py | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/insto/desktop/lookup.py b/insto/desktop/lookup.py index c8f9afc..2e4733c 100644 --- a/insto/desktop/lookup.py +++ b/insto/desktop/lookup.py @@ -305,7 +305,7 @@ def _locatable(post: Post) -> Post: """ if post.location_lat is None and post.location_lng is None: return post - if _coordinate(post.location_lat) is not None and _coordinate(post.location_lng) is not None: + if _latitude(post.location_lat) is not None and _longitude(post.location_lng) is not None: return post return replace(post, location_lat=None, location_lng=None) @@ -338,6 +338,11 @@ def _moment(value: int | None) -> int | None: return value if 0 <= value <= MAX_TIME else None +# Half the Earth's circumference along a great circle, rounded up: no two points +# on the planet are farther apart, so a larger radius is a defect, not a fact. +MAX_RADIUS_KM = 20_100.0 + + def _coordinate(value: float | None) -> float | None: if value is None or not isinstance(value, (int, float)) or isinstance(value, bool): return None @@ -345,9 +350,21 @@ def _coordinate(value: float | None) -> float | None: return number if math.isfinite(number) else None +def _latitude(value: float | None) -> float | None: + number = _coordinate(value) + return number if number is not None and -90.0 <= number <= 90.0 else None + + +def _longitude(value: float | None) -> float | None: + number = _coordinate(value) + return number if number is not None and -180.0 <= number <= 180.0 else None + + def _distance(value: float | None) -> float | None: number = _coordinate(value) - return None if number is None else round(max(number, 0.0), 3) + if number is None or number < 0.0 or number > MAX_RADIUS_KM: + return None + return round(number, 3) def _average(value: float) -> float: @@ -356,7 +373,7 @@ def _average(value: float) -> float: def _centroid(lat: float | None, lng: float | None) -> dict[str, float] | None: - latitude, longitude = _coordinate(lat), _coordinate(lng) + latitude, longitude = _latitude(lat), _longitude(lng) if latitude is None or longitude is None: return None return {"lat": latitude, "lng": longitude} diff --git a/tests/test_desktop_lookup.py b/tests/test_desktop_lookup.py index 27dda6e..4b7a8d7 100644 --- a/tests/test_desktop_lookup.py +++ b/tests/test_desktop_lookup.py @@ -737,6 +737,58 @@ async def test_a_never_terminating_cursor_stops_at_the_page_ceiling(per_page): assert result["likes"]["total"] == sum(range(result["analyzed"])) +@pytest.mark.parametrize( + "lat, lng", + [(90.5, 4.9), (-91.0, 4.9), (52.4, 180.5), (52.4, -181.0)], + ids=["lat-high", "lat-low", "lng-high", "lng-low"], +) +def test_a_coordinate_outside_its_range_is_not_a_location(lat, lng): + """The desktop host refuses such a value; the core never sends one.""" + post = lookup._locatable(make_post(0, place="Somewhere", lat=lat, lng=lng)) + assert post.location_lat is None and post.location_lng is None + assert lookup._centroid(lat, lng) is None + + +@pytest.mark.parametrize("radius", [-1.0, 20_100.5, float("inf")]) +def test_a_radius_no_place_on_earth_can_have_is_not_reported(radius): + assert lookup._distance(radius) is None + + +def test_the_largest_real_radius_and_the_poles_are_kept(): + assert lookup._distance(20_100.0) == 20_100.0 + assert lookup._centroid(90.0, -180.0) == {"lat": 90.0, "lng": -180.0} + + +async def test_lookup_activity_worst_case_is_twelve_paid_requests(): + """Six pages, each failing once and then answering: the two factors multiplied. + + `MAX_PAGE_REQUESTS` bounds the pages and the retry policy allows one more + attempt per page; the documented ceiling is their product, so this pins + the multiplication itself rather than trusting the two tests that prove + each factor alone. + """ + import httpx + + requests = [] + inner = chunk_handler(requests, per_page=1) + + def handler(request): + # Every page fails once before it answers, so each costs two requests. + attempt = sum(1 for url in requests if url == str(request.url)) + 1 + if attempt == 1: + requests.append(str(request.url)) + return httpx.Response(500, json={"detail": "provider blip"}) + return inner(request) + + backend = transport_backend(handler) + try: + result = await lookup._read(backend, "lookup.activity", {"target_pk": "7", "window": 50}) + finally: + await backend.aclose() + assert len(requests) == 2 * lookup.MAX_PAGE_REQUESTS == 12 + assert result["analyzed"] == lookup.MAX_PAGE_REQUESTS + + async def test_a_normal_page_reaches_the_largest_window_inside_the_ceiling(): requests = [] backend = transport_backend(chunk_handler(requests, per_page=12, total=120))