news: harden paywall exclusion at the candidate query + add the missing regressions

Codex's two non-blocking hardening items, folded in before cutover:
- _candidate_articles() now excludes paywalled sources IN-QUERY (before LIMIT 50),
  so flagged stories can't consume candidate slots and leave a full brief thin.
  Dropped the now-redundant post-fetch filter in build_daily_brief.
- Regressions: history retains a viewed paywalled article; sitemap omits a
  paywalled source AND restores it under override="free".
- Aligned test_brief_paywall to the source-level model (paywalled sources carry a
  paywalled homepage, as in production) — it had relied on article-URL detection.

425 backend tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-28 18:54:53 -04:00
parent c600145ba5
commit 1c1ecefde8
4 changed files with 50 additions and 9 deletions
+25
View File
@@ -43,3 +43,28 @@ def test_sitemap(client):
assert "https://upbeatbytes.com/a/1" in xml
assert "https://upbeatbytes.com/today" in xml
assert "<lastmod>2026-06-05</lastmod>" in xml
def test_sitemap_excludes_paywalled_and_restores_on_free(tmp_path, monkeypatch):
db = tmp_path / "sm.sqlite3"
monkeypatch.setenv("GOODNEWS_DB", str(db))
monkeypatch.setenv("GOODNEWS_PUBLIC_BASE_URL", "https://upbeatbytes.com")
import importlib
import goodnews.api as api
importlib.reload(api)
from goodnews.db import connect, init_db
c = connect(str(db)); init_db(c)
c.execute("INSERT INTO sources (id,name,feed_url,trust_score,content_visible) VALUES (1,'Free','http://f',5,1)")
c.execute("INSERT INTO sources (id,name,feed_url,trust_score,content_visible,paywall_override) "
"VALUES (2,'Pay','http://p',5,1,'paywalled')")
for aid, sid in [(1, 1), (2, 2)]:
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash,published_at) VALUES (?,?,?,?,?,?)",
(aid, sid, f"http://x/{aid}", f"t{aid}", f"h{aid}", "2026-06-05T08:00:00"))
c.execute("INSERT INTO article_scores (article_id,accepted) VALUES (?,1)", (aid,))
c.commit()
tc = TestClient(api.create_app())
xml = tc.get("/sitemap.xml").text
assert "/a/1</loc>" in xml and "/a/2</loc>" not in xml # paywalled source omitted
c.execute("UPDATE sources SET paywall_override='free' WHERE id=2"); c.commit()
assert "/a/2</loc>" in tc.get("/sitemap.xml").text # restored under 'free'
c.close()