Files
upbeatBytes/tests/test_digest.py
T
thejayman77 7da14bd4fd Digest email: 'Daily Highlights' masthead + warm intro, point back to the site
Per user feedback: rename from 'Today's good news' (which implied the site was
done for the day) to a publication-style 'Upbeat Bytes — Daily Highlights'
masthead with a warm morning intro and a sign-off that links back to the site
('more good news is always waiting'). Adds 'why it's here' to the plain-text
part too. No images by design (lightweight, mail-client-safe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:55:17 -04:00

65 lines
3.5 KiB
Python

from goodnews.db import connect, init_db
from goodnews import digest, email_send
def _seed(c, n=5, date="2026-06-09"):
bid = c.execute("INSERT INTO daily_briefs (brief_date, title) VALUES (?, 't')", (date,)).lastrowid
for i in range(1, n + 1):
c.execute("INSERT INTO sources (id,name,feed_url,active,content_visible) VALUES (?,?,?,1,1)",
(i, f"Src{i}", f"http://s{i}/f"))
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash) VALUES (?,?,?,?,?)",
(i, i, f"http://a/{i}", f"Title {i}", f"h{i}"))
c.execute("INSERT INTO article_scores (article_id,accepted,reason_text) VALUES (?,1,?)", (i, f"reason {i}"))
c.execute("INSERT INTO article_summaries (article_id,summary) VALUES (?,?)", (i, f"summary {i}"))
c.execute("INSERT INTO daily_brief_items (brief_id,article_id,rank) VALUES (?,?,?)", (bid, i, i))
c.execute("INSERT INTO users (id,email,digest_enabled) VALUES (1,'reader@x.com',1)")
c.commit()
return date
def test_build_digest_is_calm_and_dated():
items = [{"id": 1, "title": "Good thing", "canonical_url": "http://a/1", "source": "Src", "summary": "nice", "reason_text": "wonder", "paywalled": False}]
subject, text, html = digest.build_digest(items, "2026-06-09", "http://ub/unsub")
assert "Tuesday's Upbeat Bytes" in subject and "1 calm read" in subject
assert "Daily Highlights" in text and "Daily Highlights" in html
assert "more good news is always" in text and "http://ub/unsub" in text # points back to the site
assert "Good thing" in html and "Read on Upbeat Bytes" in html and "Unsubscribe" in html
assert "you missed" not in (text + html).lower() # no guilt language
def test_send_due_digests_sends_dedupes_and_skips(monkeypatch, tmp_path):
sent = []
monkeypatch.setattr(email_send, "send_email",
lambda to, subj, text, html=None, headers=None, **k: sent.append((to, headers)))
c = connect(str(tmp_path / "d.db")); init_db(c)
date = _seed(c, n=5)
monkeypatch.setattr(digest, "local_today", lambda: date)
# force=True bypasses the morning window
assert digest.send_due_digests(c, force=True) == 1
assert sent[0][0] == "reader@x.com"
hdrs = sent[0][1] # RFC 8058 one-click unsubscribe headers present
assert "List-Unsubscribe" in hdrs and hdrs["List-Unsubscribe-Post"] == "List-Unsubscribe=One-Click"
assert c.execute("SELECT item_count FROM digest_sends WHERE user_id=1").fetchone()[0] == 5
# dedupe: a second run sends nothing
assert digest.send_due_digests(c, force=True) == 0
# unsub token was generated on send
assert c.execute("SELECT digest_unsub_token FROM users WHERE id=1").fetchone()[0]
def test_send_skips_thin_day(monkeypatch, tmp_path):
monkeypatch.setattr(email_send, "send_email", lambda *a, **k: (_ for _ in ()).throw(AssertionError("should not send")))
c = connect(str(tmp_path / "d.db")); init_db(c)
date = _seed(c, n=3) # below MIN_ITEMS (4)
monkeypatch.setattr(digest, "local_today", lambda: date)
assert digest.send_due_digests(c, force=True) == 0
def test_send_respects_opt_out(monkeypatch, tmp_path):
sent = []
monkeypatch.setattr(email_send, "send_email", lambda to, *a, **k: sent.append(to))
c = connect(str(tmp_path / "d.db")); init_db(c)
date = _seed(c, n=5)
c.execute("UPDATE users SET digest_enabled=0 WHERE id=1"); c.commit()
monkeypatch.setattr(digest, "local_today", lambda: date)
assert digest.send_due_digests(c, force=True) == 0 and sent == []