3d9900cdfc
- Server-rendered /a/<id> "pointer" page (FastAPI): OG/Twitter meta from the
article's title/why/image, self-canonical (UB pages are the canonical for
themselves), a prominent "Read the full story at {source}" button and a quiet
"Explore more on Upbeat Bytes". No article body. Unknown/rejected/duplicate/
malformed ids → a calm 404 (no stack traces). Text-card preview when no image.
- Caddy routes /a/* to the API.
- Card Share control → menu: native Share… (where available), Copy link (the UB
card page), Copy source link. Boundary actions now hide-on-hover via a .mute
class so Save/Replace/Share stay visible. 122 tests pass.
(Event tracking for shares/opens lands with the analytics step next.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
2.4 KiB
Python
51 lines
2.4 KiB
Python
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", "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)")
|
|
# 1: accepted (renders), 2: rejected, 3: duplicate
|
|
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 articles (id,source_id,canonical_url,title,url_hash) VALUES (2,1,'https://bbc.com/y','Meh','h2')")
|
|
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash,duplicate_of) VALUES (3,1,'https://bbc.com/z','Dup','h3',1)")
|
|
c.execute("INSERT INTO article_scores (article_id,accepted,reason_text) VALUES (1,1,'A hopeful restoration story.')")
|
|
c.execute("INSERT INTO article_scores (article_id,accepted) VALUES (2,0)")
|
|
c.execute("INSERT INTO article_scores (article_id,accepted) VALUES (3,1)")
|
|
c.commit(); c.close()
|
|
return api.create_app()
|
|
|
|
|
|
def test_share_page_renders(client):
|
|
r = TestClient(client).get("/a/1")
|
|
assert r.status_code == 200
|
|
html = r.text
|
|
assert "Water voles return" in html
|
|
assert 'property="og:title"' in html and 'name="twitter:card"' in html
|
|
assert 'rel="canonical" href="https://upbeatbytes.com/a/1"' in html
|
|
assert 'https://bbc.com/x' in html # source link present
|
|
assert "A hopeful restoration story." in html
|
|
assert 'content="https://img/v.jpg"' in html # og image
|
|
|
|
|
|
def test_share_page_missing_and_malformed(client):
|
|
tc = TestClient(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_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
|