diff --git a/skills/last30days/scripts/lib/grounding.py b/skills/last30days/scripts/lib/grounding.py index ad33ede..aa06289 100644 --- a/skills/last30days/scripts/lib/grounding.py +++ b/skills/last30days/scripts/lib/grounding.py @@ -260,6 +260,7 @@ def _enrich_reddit_items(items: list[dict]) -> list[dict]: get Reddit content via web-search URLs. """ from . import reddit_enrich + from .reddit_enrich import RedditRateLimitError for item in items: url = item.get("url", "") @@ -270,7 +271,8 @@ def _enrich_reddit_items(items: list[dict]) -> list[dict]: if not thread_data: continue parsed = reddit_enrich.parse_thread_data(thread_data) - selftext = parsed.get("selftext", "") + # selftext lives under parsed["submission"], not at the top level + selftext = (parsed.get("submission") or {}).get("selftext", "") if selftext: item["snippet"] = selftext[:2000] comments = parsed.get("comments", []) @@ -281,6 +283,10 @@ def _enrich_reddit_items(items: list[dict]) -> list[dict]: for c in top[:5] ] item["enriched_via"] = "reddit_json_api" + except RedditRateLimitError as exc: + # Stop iterating to avoid flooding more 429s + sys.stderr.write(f"[Web] Reddit rate-limited, halting enrichment: {exc}\n") + break except Exception as exc: sys.stderr.write(f"[Web] Reddit enrichment failed for {url}: {exc}\n") return items diff --git a/tests/test_grounding_v3.py b/tests/test_grounding_v3.py index 0a662f5..30d663b 100644 --- a/tests/test_grounding_v3.py +++ b/tests/test_grounding_v3.py @@ -227,5 +227,51 @@ class RedditEnrichmentGateTests(unittest.TestCase): enrich_mock.assert_called_once() +class RedditEnrichItemsTests(unittest.TestCase): + """Direct tests for `_enrich_reddit_items` covering the selftext key path + and the RedditRateLimitError early-exit behavior. + """ + + def test_selftext_under_submission_populates_snippet(self): + from lib import reddit_enrich + + item = { + "url": "https://www.reddit.com/r/python/comments/abc/title/", + "snippet": "original", + } + parsed = { + "submission": {"selftext": "thread body content"}, + "comments": [], + } + with patch.object(reddit_enrich, "fetch_thread_data", return_value={"raw": True}), \ + patch.object(reddit_enrich, "parse_thread_data", return_value=parsed): + result = grounding._enrich_reddit_items([item]) + self.assertEqual("thread body content", result[0]["snippet"]) + self.assertEqual("reddit_json_api", result[0]["enriched_via"]) + + def test_rate_limit_error_halts_iteration(self): + from lib import reddit_enrich + + item1 = {"url": "https://www.reddit.com/r/python/comments/aaa/x/"} + item2 = {"url": "https://www.reddit.com/r/python/comments/bbb/y/"} + + def fake_fetch(url, *args, **kwargs): + raise reddit_enrich.RedditRateLimitError(f"429 for {url}") + + captured_stderr: list[str] = [] + + with patch.object(reddit_enrich, "fetch_thread_data", side_effect=fake_fetch) as fetch_mock, \ + patch("lib.grounding.sys.stderr.write", side_effect=lambda s: captured_stderr.append(s)): + grounding._enrich_reddit_items([item1, item2]) + + # Only the first item should have triggered a fetch attempt + self.assertEqual(1, fetch_mock.call_count) + # A stderr message about the rate-limit halt should have been emitted + self.assertTrue( + any("rate-limited" in msg.lower() or "rate limited" in msg.lower() for msg in captured_stderr), + msg=f"Expected a rate-limit stderr message, got: {captured_stderr!r}", + ) + + if __name__ == "__main__": unittest.main()