import pytest from fastapi.testclient import TestClient @pytest.fixture def client(tmp_path, monkeypatch): db = tmp_path / "t.sqlite3" monkeypatch.setenv("GOODNEWS_DB", str(db)) monkeypatch.setenv("GOODNEWS_PUBLIC_BASE_URL", "http://testserver") 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) VALUES (1,'S','http://s/f',5)") for aid in (1, 2, 3): c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash) " "VALUES (?,1,?,?,?)", (aid, f"http://s/{aid}", f"t{aid}", f"h{aid}")) c.commit(); c.close() return api.create_app(), api def _signed_in(app, api): """A TestClient with an authenticated session cookie.""" tc = TestClient(app) sent = {} import goodnews.email_send as es orig = es.send_magic_link es.send_magic_link = lambda to, link: sent.update(link=link) try: tc.post("/api/auth/email/start", json={"email": "a@b.com"}) token = sent["link"].split("token=")[1] tc.post("/api/auth/email/verify", json={"token": token}) finally: es.send_magic_link = orig return tc def test_saved_requires_auth(client): app, _ = client assert TestClient(app).get("/api/saved").status_code == 401 assert TestClient(app).post("/api/saved/1").status_code == 401 def test_save_unsave_and_list(client): app, api = client tc = _signed_in(app, api) assert tc.get("/api/saved/ids").json() == [] assert tc.post("/api/saved/1").json() == {"saved": True} assert tc.post("/api/saved/2").json() == {"saved": True} assert tc.post("/api/saved/1").json() == {"saved": True} # idempotent assert sorted(tc.get("/api/saved/ids").json()) == [1, 2] body = tc.get("/api/saved").json() assert body["count"] == 2 and {a["id"] for a in body["items"]} == {1, 2} assert tc.post("/api/saved/999").status_code == 404 # unknown article tc.delete("/api/saved/1") assert tc.get("/api/saved/ids").json() == [2] def test_history_and_import(client): app, api = client tc = _signed_in(app, api) tc.post("/api/history", json={"ids": [1, 2, 2, 999]}) # 999 ignored (FK-safe) hist = tc.get("/api/history").json() assert {a["id"] for a in hist["items"]} == {1, 2} # import folds device-local seen + saved (unknown ids dropped) tc.post("/api/import", json={"seen": [3], "saved": [1, 999]}) assert {a["id"] for a in tc.get("/api/history").json()["items"]} == {1, 2, 3} assert tc.get("/api/saved/ids").json() == [1] # granular removal from history tc.delete("/api/history/2") assert {a["id"] for a in tc.get("/api/history").json()["items"]} == {1, 3} # clear ALL history assert tc.delete("/api/history").json() == {"ok": True} assert tc.get("/api/history").json()["items"] == []