From 1a8ffd48479ec75a33c1d30ac91a39050819a45e Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Sun, 17 May 2026 00:45:06 -0700 Subject: [PATCH] fix(quality_nudge): also guard Instagram silent-failure on INCLUDE_SOURCES allowlist --- .../last30days/scripts/lib/quality_nudge.py | 10 ++- tests/test_quality_nudge.py | 62 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/skills/last30days/scripts/lib/quality_nudge.py b/skills/last30days/scripts/lib/quality_nudge.py index d0f319a..dcd5aa7 100644 --- a/skills/last30days/scripts/lib/quality_nudge.py +++ b/skills/last30days/scripts/lib/quality_nudge.py @@ -95,7 +95,15 @@ def _is_instagram_silent_failure(config: dict, research_results: dict) -> bool: for s in (config.get("EXCLUDE_SOURCES") or "").split(",") if s.strip() } - if "instagram" in excluded: + # Symmetric case: INCLUDE_SOURCES is an opt-in allowlist. If it is + # non-empty and does not name instagram, the source was intentionally + # filtered out, so a zero-item count is expected — not a silent failure. + included = { + s.strip().lower() + for s in (config.get("INCLUDE_SOURCES") or "").split(",") + if s.strip() + } + if "instagram" in excluded or (included and "instagram" not in included): return False count = research_results.get("instagram_items_count") if count is None: diff --git a/tests/test_quality_nudge.py b/tests/test_quality_nudge.py index 0ef09d2..6885986 100644 --- a/tests/test_quality_nudge.py +++ b/tests/test_quality_nudge.py @@ -515,3 +515,65 @@ class TestInstagramSilentFailure: ) assert "instagram" in q["bonus_errored"] + def test_include_sources_without_instagram_suppresses_silent_failure(self): + """User set INCLUDE_SOURCES to an opt-in allowlist that omits + instagram — the pipeline skips the source by allowlist filter, so + the zero-count instagram_items_count is intentional, not a silent + failure. Symmetric to the EXCLUDE_SOURCES=instagram guard. + """ + q = _compute( + config_overrides={ + "AUTH_TOKEN": "tok123", + "SCRAPECREATORS_API_KEY": "sc_key", + "INCLUDE_SOURCES": "reddit,hn,x,youtube", + }, + ytdlp_installed=True, + result_overrides={"instagram_items_count": 0}, + ) + assert "instagram" not in q["bonus_errored"] + assert q["nudge_text"] is None + + def test_include_sources_multi_value_without_instagram(self): + """Canonical parsing pattern is comma-separated; case-insensitive.""" + q = _compute( + config_overrides={ + "AUTH_TOKEN": "tok123", + "SCRAPECREATORS_API_KEY": "sc_key", + "INCLUDE_SOURCES": " Reddit, HN , YouTube ", + }, + ytdlp_installed=True, + result_overrides={"instagram_items_count": 0}, + ) + assert "instagram" not in q["bonus_errored"] + + def test_include_sources_with_instagram_still_flags(self): + """INCLUDE_SOURCES that explicitly names instagram must not suppress + the silent-failure nudge — the source was opted in, so a zero count + is a real silent failure. + """ + q = _compute( + config_overrides={ + "AUTH_TOKEN": "tok123", + "SCRAPECREATORS_API_KEY": "sc_key", + "INCLUDE_SOURCES": "reddit,instagram", + }, + ytdlp_installed=True, + result_overrides={"instagram_items_count": 0}, + ) + assert "instagram" in q["bonus_errored"] + + def test_include_sources_empty_does_not_suppress(self): + """Empty/unset INCLUDE_SOURCES means no allowlist filter, so the + silent-failure gate should still fire when instagram is zero. + """ + q = _compute( + config_overrides={ + "AUTH_TOKEN": "tok123", + "SCRAPECREATORS_API_KEY": "sc_key", + "INCLUDE_SOURCES": "", + }, + ytdlp_installed=True, + result_overrides={"instagram_items_count": 0}, + ) + assert "instagram" in q["bonus_errored"] +