Files
upbeatBytes/tests/test_replacement.py
thejayman77 bfd612eb9b Paywall awareness (#6) + replace-an-article (#7)
- paywall.py: conservative domain-level paywall detection (New Scientist,
  Nature, and common hard/soft paywalls). Never fetches pages — an honest hint.
- API: Article gains a 'paywalled' flag; the brief now leads with a gentle AND
  readable story (paywalled/charged stories stay in the five, just not first).
- New GET /api/replacement returns the next-best readable, unshown article
  (honors mood+prefs via the merged prefs param; gentle=true for hero swaps).
- UI: paywalled cards show 'May need a subscription'; a Replace / 'Find one I
  can read' action (always visible, while tuning actions stay tucked) swaps the
  card for a readable alternative, with a gentle notice when none remain.
- Tests: paywall detection + replacement behavior (77 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:39:13 +00:00

38 lines
1.8 KiB
Python

import pytest
from fastapi.testclient import TestClient
from goodnews.db import connect, init_db
@pytest.fixture
def client(tmp_path, monkeypatch):
db = tmp_path / "t.sqlite3"
monkeypatch.setenv("GOODNEWS_DB", str(db))
conn = connect(db); init_db(conn)
conn.execute("INSERT INTO sources (id,name,feed_url,trust_score) VALUES (1,'S','http://s/f',6)")
def add(aid, url, cort=1):
conn.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash) VALUES (?,1,?,?,?)",
(aid, url, f"t{aid}", f"h{aid}"))
conn.execute("INSERT INTO article_scores (article_id,constructive_score,agency_score,human_benefit_score,"
"cortisol_score,ragebait_score,pr_risk_score,accepted,topic,flavor) "
"VALUES (?,7,2,2,?,0,2,1,'science','discovery')", (aid, cort))
add(1, "https://phys.org/free-a") # free, best
add(2, "https://www.newscientist.com/pay") # paywalled
add(3, "https://www.goodnewsnetwork.org/free-b") # free
conn.commit(); conn.close()
from goodnews.api import create_app
return TestClient(create_app())
def test_replacement_skips_paywalled_and_excluded(client):
# exclude the top free one -> should return the OTHER free one, never the paywalled
r = client.get("/api/replacement", params={"exclude": "1"})
assert r.status_code == 200
body = r.json()
assert body is not None and body["id"] == 3 and body["paywalled"] is False
def test_replacement_none_when_only_paywalled_left(client):
r = client.get("/api/replacement", params={"exclude": "1,3"})
assert r.json() is None # only the paywalled one remains, and avoid_paywall defaults true
def test_replacement_can_include_paywalled_when_allowed(client):
r = client.get("/api/replacement", params={"exclude": "1,3", "avoid_paywall": "false"})
assert r.json()["id"] == 2