images/analytics: purge on policy revoke + engagement warm-up note (Codex close-out)
- newsimg.purge_source(): when a source leaves 'cache' (permission revoked / re-classified),
the admin image-policy endpoint now deletes that source's re-hosted copies immediately,
rather than leaving them inaccessible-but-on-disk. Endpoint returns {purged}.
- Admin "Engaged readers" carries a warm-up note: tracking began 2026-06-30, so low
rolling windows are partly warm-up, not all bots (compare d7 after a week, the window
after its full span). Guards against misreading "6 engaged vs 135 visits" as 129 bots.
Tests: purge_source removes only the target source's copies; endpoint reports purged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -600,6 +600,7 @@ def test_source_image_policy(tmp_path, monkeypatch):
|
||||
c = sqlite3.connect(os.environ["GOODNEWS_DB"])
|
||||
assert c.execute("SELECT image_policy FROM sources WHERE id=2").fetchone()[0] == "cache"
|
||||
c.close()
|
||||
assert tc.post("/api/admin/sources/2/image-policy", json={"policy": "remote"}).json()["policy"] == "remote"
|
||||
r = tc.post("/api/admin/sources/2/image-policy", json={"policy": "remote"}).json()
|
||||
assert r["policy"] == "remote" and r["purged"] == 0 # leaving cache purges (no files here)
|
||||
assert tc.post("/api/admin/sources/2/image-policy", json={"policy": "bogus"}).status_code == 422
|
||||
assert tc.post("/api/admin/sources/999/image-policy", json={"policy": "cache"}).status_code == 404
|
||||
|
||||
@@ -193,6 +193,22 @@ def test_warm_only_caches_cache_policy_sources(cache, monkeypatch):
|
||||
assert newsimg.warm(conn) == 0 # idempotent — already cached
|
||||
|
||||
|
||||
def test_purge_source_removes_cached_copies(cache, monkeypatch):
|
||||
conn = connect(":memory:"); init_db(conn)
|
||||
conn.execute("INSERT INTO sources (id,name,feed_url,image_policy) VALUES (1,'C','http://c/f','cache')")
|
||||
conn.execute("INSERT INTO sources (id,name,feed_url,image_policy) VALUES (2,'O','http://o/f','cache')")
|
||||
for aid, sid, img in ((1, 1, "https://x/1.jpg"), (2, 1, "https://x/2.jpg"), (3, 2, "https://x/3.jpg")):
|
||||
conn.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash,image_url) "
|
||||
"VALUES (?,?,?,?,?,?)", (aid, sid, f"http://s/{aid}", f"t{aid}", f"h{aid}", img))
|
||||
conn.execute("INSERT INTO article_scores (article_id, accepted) VALUES (?,1)", (aid,))
|
||||
conn.commit()
|
||||
assert newsimg.warm(conn) == 3 # all three cached (both sources 'cache')
|
||||
removed = newsimg.purge_source(conn, 1) # source 1 leaves cache → its 2 copies go
|
||||
assert removed == 2
|
||||
assert newsimg.path_for("https://x/1.jpg") is None and newsimg.path_for("https://x/2.jpg") is None
|
||||
assert newsimg.path_for("https://x/3.jpg") is not None # source 2 untouched
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("GOODNEWS_DB", str(tmp_path / "t.sqlite3"))
|
||||
|
||||
Reference in New Issue
Block a user