"""Tests for post-research quality score and upgrade nudge.""" import pytest from unittest.mock import patch # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _base_config(**overrides): """Return a minimal config dict.""" config = { "AUTH_TOKEN": None, "CT0": None, "XAI_API_KEY": None, "SCRAPECREATORS_API_KEY": None, } config.update(overrides) return config def _base_results(**overrides): """Return a minimal research_results dict with no errors.""" results = { "x_error": None, "youtube_error": None, "reddit_error": None, } results.update(overrides) return results def _compute(config_overrides=None, result_overrides=None, ytdlp_installed=False): """Helper to call compute_quality_score with mocked yt-dlp check.""" from scripts.lib.quality_nudge import compute_quality_score from scripts.lib import youtube_yt config = _base_config(**(config_overrides or {})) results = _base_results(**(result_overrides or {})) with patch.object(youtube_yt, "is_ytdlp_installed", return_value=ytdlp_installed): return compute_quality_score(config, results) # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestBaseline: """HN + Polymarket only (no X, no YT, no SC) -> 40%.""" def test_score_40(self): q = _compute() assert q["score_pct"] == 40 def test_active_sources(self): q = _compute() assert "hn" in q["core_active"] assert "polymarket" in q["core_active"] assert len(q["core_active"]) == 2 def test_missing_all_three(self): q = _compute() assert set(q["core_missing"]) == {"x", "youtube", "reddit_comments"} def test_nudge_mentions_all_missing(self): q = _compute() assert q["nudge_text"] is not None assert "X/Twitter" in q["nudge_text"] assert "YouTube" in q["nudge_text"] assert "Reddit with comments" in q["nudge_text"] class TestXCookies: """+X cookies -> 60%.""" def test_score_60(self): q = _compute(config_overrides={"AUTH_TOKEN": "tok123"}) assert q["score_pct"] == 60 def test_nudge_mentions_yt_and_sc(self): q = _compute(config_overrides={"AUTH_TOKEN": "tok123"}) assert "YouTube" in q["nudge_text"] assert "Reddit with comments" in q["nudge_text"] assert "X/Twitter" not in q["nudge_text"] class TestXPlusYtdlp: """+X + yt-dlp -> 80%.""" def test_score_80(self): q = _compute( config_overrides={"AUTH_TOKEN": "tok123"}, ytdlp_installed=True, ) assert q["score_pct"] == 80 def test_nudge_mentions_sc_only(self): q = _compute( config_overrides={"AUTH_TOKEN": "tok123"}, ytdlp_installed=True, ) assert "ScrapeCreators" in q["nudge_text"] or "scrapecreators" in q["nudge_text"] assert "YouTube" not in q["nudge_text"] assert "X/Twitter" not in q["nudge_text"] class TestFullCoverage: """+X + yt-dlp + SC -> 100%, no nudge.""" def test_score_100(self): q = _compute( config_overrides={ "AUTH_TOKEN": "tok123", "SCRAPECREATORS_API_KEY": "sc_key", }, ytdlp_installed=True, ) assert q["score_pct"] == 100 def test_nudge_is_none(self): q = _compute( config_overrides={ "AUTH_TOKEN": "tok123", "SCRAPECREATORS_API_KEY": "sc_key", }, ytdlp_installed=True, ) assert q["nudge_text"] is None class TestSCActiveNoX: """SC active but no X -> 80%, nudge suggests browser cookies (free).""" def test_score_80(self): q = _compute( config_overrides={"SCRAPECREATORS_API_KEY": "sc_key"}, ytdlp_installed=True, ) assert q["score_pct"] == 80 def test_nudge_suggests_browser_cookies(self): q = _compute( config_overrides={"SCRAPECREATORS_API_KEY": "sc_key"}, ytdlp_installed=True, ) assert q["nudge_text"] is not None assert "browser" in q["nudge_text"].lower() assert "x.com" in q["nudge_text"].lower() class TestRedditErrored: """SC is configured but Reddit errored this run.""" def test_nudge_mentions_error(self): q = _compute( config_overrides={ "AUTH_TOKEN": "tok123", "SCRAPECREATORS_API_KEY": "sc_key", }, result_overrides={"reddit_error": "ScrapeCreators: 500 Internal Server Error"}, ytdlp_installed=True, ) assert "reddit_comments" in q["core_errored"] assert "errored" in q["nudge_text"].lower() class TestDisclaimerAlwaysPresent: """Nudge always includes no-affiliate disclaimer when present.""" def test_disclaimer_baseline(self): q = _compute() assert "no affiliation" in q["nudge_text"] def test_disclaimer_partial(self): q = _compute(config_overrides={"AUTH_TOKEN": "tok123"}) assert "no affiliation" in q["nudge_text"] def test_disclaimer_not_present_at_100(self): q = _compute( config_overrides={ "AUTH_TOKEN": "tok123", "SCRAPECREATORS_API_KEY": "sc_key", }, ytdlp_installed=True, ) assert q["nudge_text"] is None class TestSCNudgeContent: """SC nudge always includes '100 free API calls, no credit card'.""" def test_sc_nudge_content(self): q = _compute() assert "100 free API calls, no credit card" in q["nudge_text"] def test_sc_nudge_content_when_only_missing_sc(self): q = _compute( config_overrides={"AUTH_TOKEN": "tok123"}, ytdlp_installed=True, ) assert "100 free API calls, no credit card" in q["nudge_text"]