feat: v2.9.6 — free-first NUX, cookie extraction, quality scoring
Setup wizard with consent-first cookie extraction (Chrome/Firefox/Safari), yt-dlp auto-install, ScrapeCreators push, quality scoring (5 core sources), status banner redesign, honest Reddit labeling, inline YouTube transcripts, Exa free web search, Reddit public fallback, and post-research quality nudge. Co-authored-by: Matt Van Horn <mvanhorn@MacBook-Pro.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user