fix(quality_nudge,bluesky): gate Instagram nudge on EXCLUDE_SOURCES + anchor bluesky tests at resolver
This commit is contained in:
@@ -24,7 +24,6 @@ from . import http, log
|
|||||||
|
|
||||||
BSKY_SESSION_URL = "https://bsky.social/xrpc/com.atproto.server.createSession"
|
BSKY_SESSION_URL = "https://bsky.social/xrpc/com.atproto.server.createSession"
|
||||||
_DEFAULT_BSKY_SEARCH_HOST = "api.bsky.app"
|
_DEFAULT_BSKY_SEARCH_HOST = "api.bsky.app"
|
||||||
BSKY_SEARCH_URL = f"https://{_DEFAULT_BSKY_SEARCH_HOST}/xrpc/app.bsky.feed.searchPosts"
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_search_url(config: Optional[Dict[str, Any]] = None) -> str:
|
def _resolve_search_url(config: Optional[Dict[str, Any]] = None) -> str:
|
||||||
|
|||||||
@@ -86,6 +86,17 @@ def _is_instagram_silent_failure(config: dict, research_results: dict) -> bool:
|
|||||||
"""
|
"""
|
||||||
if not config.get("SCRAPECREATORS_API_KEY"):
|
if not config.get("SCRAPECREATORS_API_KEY"):
|
||||||
return False # not configured — not a silent failure
|
return False # not configured — not a silent failure
|
||||||
|
# Honor EXCLUDE_SOURCES: a user who set EXCLUDE_SOURCES=instagram
|
||||||
|
# intentionally turned the source off, so a zero-item count is
|
||||||
|
# expected, not a silent failure. Mirror the canonical parsing
|
||||||
|
# pattern from pipeline.available_sources().
|
||||||
|
excluded = {
|
||||||
|
s.strip().lower()
|
||||||
|
for s in (config.get("EXCLUDE_SOURCES") or "").split(",")
|
||||||
|
if s.strip()
|
||||||
|
}
|
||||||
|
if "instagram" in excluded:
|
||||||
|
return False
|
||||||
count = research_results.get("instagram_items_count")
|
count = research_results.get("instagram_items_count")
|
||||||
if count is None:
|
if count is None:
|
||||||
return False # source not run this invocation
|
return False # source not run this invocation
|
||||||
|
|||||||
+11
-6
@@ -231,13 +231,18 @@ class TestSearchEndpointHostResolution(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
os.environ.pop("BSKY_SEARCH_HOST", None)
|
os.environ.pop("BSKY_SEARCH_HOST", None)
|
||||||
|
|
||||||
def test_module_constant_uses_canonical_appview(self):
|
def test_resolver_default_uses_canonical_appview(self):
|
||||||
# Regression guard against the public mirror reappearing as the default
|
# Regression guard against the public mirror reappearing as the default.
|
||||||
self.assertIn("api.bsky.app", bluesky.BSKY_SEARCH_URL)
|
# Anchored at the resolver because that is the code path search_bluesky
|
||||||
|
# actually calls; a module-level constant would not catch a resolver
|
||||||
|
# regression.
|
||||||
|
self.assertIn("api.bsky.app", bluesky._resolve_search_url())
|
||||||
|
|
||||||
def test_module_constant_does_not_use_public_mirror(self):
|
def test_resolver_default_does_not_use_public_mirror(self):
|
||||||
# Hard regression guard — the exact host that BunnyCDN was blocking
|
# Hard regression guard — the exact host that BunnyCDN was blocking.
|
||||||
self.assertNotIn("public.api.bsky.app", bluesky.BSKY_SEARCH_URL)
|
# Asserted at the resolver level (the runtime path) so a default-host
|
||||||
|
# regression in _resolve_search_url is actually caught.
|
||||||
|
self.assertNotIn("public.api.bsky.app", bluesky._resolve_search_url())
|
||||||
|
|
||||||
def test_resolver_default_when_no_override(self):
|
def test_resolver_default_when_no_override(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@@ -469,3 +469,49 @@ class TestInstagramSilentFailure:
|
|||||||
q = _compute()
|
q = _compute()
|
||||||
assert q.get("bonus_errored") == []
|
assert q.get("bonus_errored") == []
|
||||||
|
|
||||||
|
def test_exclude_sources_instagram_suppresses_silent_failure(self):
|
||||||
|
"""User set EXCLUDE_SOURCES=instagram - the source intentionally did
|
||||||
|
not run, so the zero-count instagram_items_count written by
|
||||||
|
last30days.py is a non-event, not a silent failure. Pre-fix: the
|
||||||
|
nudge fired anyway because the gate only checked SC-key + count.
|
||||||
|
"""
|
||||||
|
q = _compute(
|
||||||
|
config_overrides={
|
||||||
|
"AUTH_TOKEN": "tok123",
|
||||||
|
"SCRAPECREATORS_API_KEY": "sc_key",
|
||||||
|
"EXCLUDE_SOURCES": "instagram",
|
||||||
|
},
|
||||||
|
ytdlp_installed=True,
|
||||||
|
result_overrides={"instagram_items_count": 0},
|
||||||
|
)
|
||||||
|
assert "instagram" not in q["bonus_errored"]
|
||||||
|
assert q["nudge_text"] is None
|
||||||
|
|
||||||
|
def test_exclude_sources_multi_value_with_instagram(self):
|
||||||
|
"""Canonical parsing pattern is comma-separated; case-insensitive."""
|
||||||
|
q = _compute(
|
||||||
|
config_overrides={
|
||||||
|
"AUTH_TOKEN": "tok123",
|
||||||
|
"SCRAPECREATORS_API_KEY": "sc_key",
|
||||||
|
"EXCLUDE_SOURCES": "threads, Instagram , pinterest",
|
||||||
|
},
|
||||||
|
ytdlp_installed=True,
|
||||||
|
result_overrides={"instagram_items_count": 0},
|
||||||
|
)
|
||||||
|
assert "instagram" not in q["bonus_errored"]
|
||||||
|
|
||||||
|
def test_exclude_sources_other_value_still_flags(self):
|
||||||
|
"""EXCLUDE_SOURCES that does not mention instagram must not suppress
|
||||||
|
the silent-failure nudge for instagram.
|
||||||
|
"""
|
||||||
|
q = _compute(
|
||||||
|
config_overrides={
|
||||||
|
"AUTH_TOKEN": "tok123",
|
||||||
|
"SCRAPECREATORS_API_KEY": "sc_key",
|
||||||
|
"EXCLUDE_SOURCES": "threads",
|
||||||
|
},
|
||||||
|
ytdlp_installed=True,
|
||||||
|
result_overrides={"instagram_items_count": 0},
|
||||||
|
)
|
||||||
|
assert "instagram" in q["bonus_errored"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user