"""Regression tests for news geocoding keywords and feed configuration.""" import json from pathlib import Path import pytest from services.fetchers.news import _resolve_coords from services.news_feed_config import DEFAULT_FEEDS, _normalise_feeds CONFIG_PATH = Path(__file__).parent.parent / "config" / "news_feeds.json" # -- Keyword resolution: East Asia specific locations -------------------------- class TestResolveCoords: """_resolve_coords should prefer longer (more specific) keywords.""" def test_taiwan_strait_not_absorbed_by_taiwan(self): result = _resolve_coords("tensions in the taiwan strait") assert result == (24.0, 119.5) def test_south_china_sea_not_absorbed_by_china(self): result = _resolve_coords("south china sea patrol") assert result == (15.0, 115.0) def test_east_china_sea(self): result = _resolve_coords("east china sea tensions") assert result == (28.0, 125.0) def test_philippine_sea(self): result = _resolve_coords("philippine sea exercises") assert result == (20.0, 130.0) def test_generic_china_still_works(self): result = _resolve_coords("china deploys forces") assert result == (35.861, 104.195) def test_generic_taiwan_still_works(self): result = _resolve_coords("taiwan elections") assert result == (23.697, 120.960) def test_taipei(self): result = _resolve_coords("protests in taipei") assert result == (25.033, 121.565) def test_okinawa(self): result = _resolve_coords("okinawa base expansion") assert result == (26.334, 127.800) # -- Existing inclusion-relationship regressions --------------------------- def test_new_delhi_not_absorbed_by_delhi(self): result = _resolve_coords("new delhi summit") assert result == (28.613, 77.209) def test_south_america_not_absorbed_by_america(self): result = _resolve_coords("south america trade deal") assert result == (-14.200, -51.900) def test_north_korea_not_absorbed_by_south_korea(self): result = _resolve_coords("north korea missile launch") assert result == (40.339, 127.510) # -- Space-padded keywords ------------------------------------------------- def test_us_with_spaces(self): result = _resolve_coords("the us military") assert result == (38.907, -77.036) def test_uk_with_spaces(self): result = _resolve_coords("visit the uk soon") assert result == (55.378, -3.435) # -- New East Asia island/strait keywords ------------------------------------ def test_pratas(self): assert _resolve_coords("china patrols near pratas islands") == (20.71, 116.72) def test_dongsha(self): assert _resolve_coords("dongsha atoll tensions") == (20.71, 116.72) def test_kinmen(self): assert _resolve_coords("artillery drill near kinmen") == (24.45, 118.38) def test_matsu(self): assert _resolve_coords("matsu island cable cut") == (26.16, 119.94) def test_scarborough(self): assert _resolve_coords("scarborough shoal standoff") == (15.14, 117.77) def test_paracel(self): assert _resolve_coords("paracel islands dispute") == (16.50, 112.00) def test_spratly(self): assert _resolve_coords("spratly island reclamation") == (10.00, 114.00) def test_miyako_strait(self): assert _resolve_coords("PLAN warships transit miyako strait") == (24.78, 125.30) def test_bashi_channel(self): assert _resolve_coords("submarine detected in bashi channel") == (21.00, 121.50) def test_luzon_strait(self): assert _resolve_coords("luzon strait patrol") == (20.50, 121.50) def test_dmz(self): assert _resolve_coords("tension at the dmz border") == (38.00, 127.00) def test_yalu(self): assert _resolve_coords("troops near yalu river") == (40.00, 124.40) def test_yongbyon(self): assert _resolve_coords("activity at yongbyon reactor") == (39.80, 125.76) def test_wonsan(self): assert _resolve_coords("missile launch from wonsan") == (39.18, 127.48) def test_busan(self): assert _resolve_coords("naval exercise near busan port") == (35.18, 129.07) # -- No match -------------------------------------------------------------- def test_no_match_returns_none(self): result = _resolve_coords("unknown location xyz") assert result is None # -- Feed configuration consistency ------------------------------------------- class TestFeedConfig: """DEFAULT_FEEDS and news_feeds.json must stay in sync.""" def test_default_feeds_match_json(self): data = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) json_feeds = data["feeds"] def normalize(feeds): return sorted( [{"name": f["name"], "url": f["url"], "weight": f["weight"]} for f in feeds], key=lambda f: f["name"], ) assert normalize(DEFAULT_FEEDS) == normalize(json_feeds) def test_new_east_asia_feeds_present(self): names = {f["name"] for f in DEFAULT_FEEDS} expected = {"SCMP", "The Diplomat", "Yonhap", "Asia Times", "Defense News", "Japan Times"} assert expected.issubset(names) def test_known_dead_feeds_are_not_defaulted(self): urls = {f["url"] for f in DEFAULT_FEEDS} assert "https://www.reutersagency.com/feed/?best-topics=world" not in urls assert "https://rsshub.app/apnews/topics/world-news" not in urls def test_legacy_http_feeds_are_migrated_to_https(self): feeds = _normalise_feeds( [ {"name": "BBC", "url": "http://feeds.bbci.co.uk/news/world/rss.xml", "weight": 3}, {"name": "Xinhua", "url": "http://www.news.cn/english/rss/worldrss.xml", "weight": 2}, ] ) urls = {f["url"] for f in feeds} assert "https://feeds.bbci.co.uk/news/world/rss.xml" in urls assert "https://www.news.cn/english/rss/worldrss.xml" in urls