Sync repo to deployed state: SEO recovery, Publishing Desk, Play games, emoji picker
The deploy pipeline runs from the working tree, so a wave of shipped features
had never been committed. This snapshots git to what's actually running.
SEO impression recovery (live + verified):
- Duplicate /a/{id} now 301-redirect to their canonical twin instead of 404
(a hard 404 silently dropped already-indexed URLs and tanked impressions).
- Dedup representative selection reworked: accepted/serveable -> established
rep (URL stability) -> quality score, so an accepted page never retires to a
rejected rep and an indexed canonical doesn't churn when a newer twin arrives.
- HEAD /a/{id} returns the same status as GET (api_route GET+HEAD) instead of
falling through to the static mount and 404ing.
- `dedup --force-recluster`: cycle-locked, model-free re-cluster to re-apply the
policy to the existing corpus (shared cycle_lock context manager).
- CLI honors GOODNEWS_DB for its default --db (was silently ignored).
Publishing Desk (admin tool to post highlights to X via Web Intents):
- publishing.py queue/rank/handle-resolution; admin UI; full searchable emoji
picker (bundled data, no CDN) for the blurb editor.
Play games + site:
- Bloom (word-wheel), Memory Match, daily ritual set, Zen Den (dev-gated).
- English-only language gate; source prospecting; paywall + dedup hardening.
Tests: full suite green (349). Ignores tightened (node_modules, data/*.db).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+48
-1
@@ -42,9 +42,56 @@ def test_share_page_missing_and_malformed(client):
|
||||
assert tc.get("/a/999").status_code == 404 # unknown
|
||||
assert tc.get("/a/not-a-number").status_code == 404 # malformed → calm 404
|
||||
assert tc.get("/a/2").status_code == 404 # rejected article
|
||||
assert tc.get("/a/3").status_code == 404 # duplicate
|
||||
|
||||
|
||||
def test_share_page_duplicate_redirects_to_canonical(client):
|
||||
# article 3 is a duplicate of the live article 1 — its URL may be indexed, so it
|
||||
# 301s to the canonical (consolidates) rather than 404ing and dropping from Google.
|
||||
r = TestClient(client).get("/a/3", follow_redirects=False)
|
||||
assert r.status_code == 301 and r.headers["location"] == "/a/1"
|
||||
|
||||
|
||||
def test_share_page_no_image_uses_summary_card(client, tmp_path, monkeypatch):
|
||||
# article 1 has an image → large card
|
||||
assert 'summary_large_image' in TestClient(client).get("/a/1").text
|
||||
|
||||
|
||||
def test_incomplete_page_is_not_cached(client):
|
||||
# article 1 has no summary/explanation → "generating" page must not be cached,
|
||||
# and carries no-cache so it re-fetches once the summary lands.
|
||||
import goodnews.api as api
|
||||
r = TestClient(client).get("/a/1")
|
||||
assert r.status_code == 200
|
||||
assert r.headers.get("cache-control") == "no-cache"
|
||||
assert 1 not in api._SHARE_CACHE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_complete(tmp_path, monkeypatch):
|
||||
db = tmp_path / "t.sqlite3"
|
||||
monkeypatch.setenv("GOODNEWS_DB", str(db))
|
||||
monkeypatch.setenv("GOODNEWS_PUBLIC_BASE_URL", "https://upbeatbytes.com")
|
||||
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,'BBC','http://s/f',5)")
|
||||
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash,image_url) "
|
||||
"VALUES (1,1,'https://bbc.com/x','Water voles return','h1','https://img/v.jpg')")
|
||||
c.execute("INSERT INTO article_scores (article_id,accepted,reason_text) VALUES (1,1,'Hopeful.')")
|
||||
c.execute("INSERT INTO article_summaries (article_id,summary,what_happened,why_matters,why_belongs) "
|
||||
"VALUES (1,'Voles are back.','They returned to the river.','Biodiversity rebound.','Quietly hopeful.')")
|
||||
c.commit(); c.close()
|
||||
return api
|
||||
|
||||
|
||||
def test_complete_page_is_cached_and_served_from_cache(app_complete):
|
||||
api = app_complete
|
||||
tc = TestClient(api.create_app())
|
||||
r1 = tc.get("/a/1")
|
||||
assert r1.status_code == 200
|
||||
assert r1.headers.get("cache-control") == "public, max-age=300"
|
||||
assert 1 in api._SHARE_CACHE # finished page cached
|
||||
r2 = tc.get("/a/1") # second hit served from cache
|
||||
assert r2.status_code == 200 and r2.text == r1.text
|
||||
|
||||
Reference in New Issue
Block a user