import json 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, topic, in_brief_rank=None, title=None): conn.execute("INSERT INTO articles (id,source_id,canonical_url,title,published_at,url_hash) " "VALUES (?,1,?,?, '2026-05-31T10:00:00+00:00', ?)", (aid, f"http://s/{aid}", title or 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,1,0,2,1,?,'solution')", (aid, topic)) if in_brief_rank: conn.execute("INSERT INTO daily_brief_items (brief_id,article_id,rank) VALUES (1,?,?)", (aid, in_brief_rank)) conn.execute("INSERT INTO daily_briefs (id,brief_date,title) VALUES (1,'2026-05-31','B')") add(1, "health", 1) # will be muted add(2, "science", 2) add(3, "environment", 3) add(4, "community") # refill candidates (accepted, not in brief) add(5, "animals") add(6, "culture") conn.commit(); conn.close() from goodnews.api import create_app return TestClient(create_app()) def test_brief_refills_to_full_count_under_boundary(client): full = client.get("/api/brief", params={"limit": 3}).json() assert len(full["items"]) == 3 muted = client.get("/api/brief", params={"limit": 3, "prefs": json.dumps({"mute_topics": ["health"]})}).json() topics = [i["topic"] for i in muted["items"]] assert len(muted["items"]) == 3 # stayed full (refilled) assert "health" not in topics # boundary respected def test_brief_refill_respects_avoid_terms(client): # avoid a word in the health item's title client_resp = client.get("/api/brief", params={"limit": 3, "prefs": json.dumps({"avoid_terms": ["t1"]})}).json() assert len(client_resp["items"]) == 3 assert all(i["id"] != 1 for i in client_resp["items"]) def test_brief_excludes_dismissed_and_refills(client): # Dismiss the first brief item; the highlights should stay full and not show it. out = client.get("/api/brief", params={"limit": 3, "exclude": "1"}).json() ids = [i["id"] for i in out["items"]] assert len(out["items"]) == 3 assert 1 not in ids