d472b63cbf
A stored og:image isn't proof it renders: signed/hotlink-protected URLs (e.g. the Guardian's i.guim.co.uk) 401 on a direct browser load, so they counted toward coverage yet always fell back. Now fetch_og_image confirms the image truly returns 200 + image/* (requested no-referrer, same SSRF-safe redirect handling) before storing it. Add prune_broken_images() to clear already-stored URLs that no longer load, so coverage is honest and those cards show the placeholder cleanly. The browser onerror→placeholder remains the final safety net. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
3.3 KiB
Python
71 lines
3.3 KiB
Python
from goodnews.db import connect, init_db
|
|
from goodnews import enrich
|
|
|
|
|
|
def _setup(tmp_path):
|
|
c = connect(str(tmp_path / "t.db")); init_db(c)
|
|
c.execute("INSERT INTO sources (id,name,feed_url) VALUES (1,'S','http://s/f')")
|
|
return c
|
|
|
|
|
|
def _add(c, aid, *, image=None, accepted=1, dup=None, summarized=True):
|
|
c.execute(
|
|
"INSERT INTO articles (id,source_id,canonical_url,title,url_hash,image_url,duplicate_of,published_at) "
|
|
"VALUES (?,1,?,?,?,?,?,datetime('now'))",
|
|
(aid, f"http://s/{aid}", f"T{aid}", f"h{aid}", image, dup),
|
|
)
|
|
c.execute("INSERT INTO article_scores (article_id,accepted) VALUES (?,?)", (aid, accepted))
|
|
if summarized:
|
|
c.execute("INSERT INTO article_summaries (article_id,summary) VALUES (?, 's')", (aid,))
|
|
c.commit()
|
|
|
|
|
|
def test_enrich_article_image_fills_missing(tmp_path):
|
|
c = _setup(tmp_path); _add(c, 1, image=None)
|
|
got = enrich.enrich_article_image(c, 1, fetch=lambda url: "http://img/og.jpg")
|
|
assert got is True
|
|
assert c.execute("SELECT image_url FROM articles WHERE id=1").fetchone()[0] == "http://img/og.jpg"
|
|
# image_checked_at is stamped so we don't re-fetch endlessly
|
|
assert c.execute("SELECT image_checked_at FROM articles WHERE id=1").fetchone()[0] is not None
|
|
|
|
|
|
def test_enrich_skips_when_image_present(tmp_path):
|
|
c = _setup(tmp_path); _add(c, 1, image="http://existing/img.jpg")
|
|
calls = []
|
|
got = enrich.enrich_article_image(c, 1, fetch=lambda url: calls.append(url) or "http://new.jpg")
|
|
assert got is False and calls == [] # never fetched
|
|
assert c.execute("SELECT image_url FROM articles WHERE id=1").fetchone()[0] == "http://existing/img.jpg"
|
|
|
|
|
|
def test_enrich_records_checked_even_when_none_found(tmp_path):
|
|
c = _setup(tmp_path); _add(c, 1, image=None)
|
|
got = enrich.enrich_article_image(c, 1, fetch=lambda url: None)
|
|
assert got is False
|
|
# checked_at stamped → won't retry until the retry window passes
|
|
assert c.execute("SELECT image_checked_at FROM articles WHERE id=1").fetchone()[0] is not None
|
|
again = enrich.enrich_article_image(c, 1, fetch=lambda url: "http://late.jpg", retry_days=7)
|
|
assert again is False # still within retry window
|
|
|
|
|
|
def test_prune_clears_only_broken_images(tmp_path):
|
|
c = _setup(tmp_path)
|
|
_add(c, 1, image="http://good/a.jpg")
|
|
_add(c, 2, image="http://broken/b.jpg")
|
|
_add(c, 3, image=None) # nothing to check
|
|
# Pretend only the 'good' host loads.
|
|
cleared = enrich.prune_broken_images(c, check=lambda u: "good" in u)
|
|
assert cleared == 1
|
|
assert c.execute("SELECT image_url FROM articles WHERE id=1").fetchone()[0] == "http://good/a.jpg"
|
|
assert c.execute("SELECT image_url FROM articles WHERE id=2").fetchone()[0] is None
|
|
|
|
|
|
def test_backfill_only_targets_summarized_accepted_imageless(tmp_path):
|
|
c = _setup(tmp_path)
|
|
_add(c, 1, image=None, summarized=True, accepted=1) # eligible
|
|
_add(c, 2, image=None, summarized=False, accepted=1) # no summary → skip
|
|
_add(c, 3, image=None, summarized=True, accepted=0) # rejected → skip
|
|
_add(c, 4, image="http://has.jpg", summarized=True) # has image → skip
|
|
n = enrich.enrich_summarized_images(c, fetch=lambda url: "http://og.jpg", limit=50)
|
|
assert n == 1
|
|
assert c.execute("SELECT image_url FROM articles WHERE id=2").fetchone()[0] is None
|