Files
upbeatBytes/tests/test_events.py
T
thejayman77 1a778e1334 Admin step A: privacy-respecting first-party event logging
- events table (kind, article_id, visitor_hash, day) with a UNIQUE key that dedups
  to one row per visitor-day — caps volume and makes counts mean distinct
  visitor-days. NO ip/ua/referrer/url. Groupings derived from article_id at query
  time, never stored.
- POST /api/events (public): whitelisted kinds (visit/open/share_ub/copy_source/
  native_share/source_click); visitor token hashed server-side (never raw).
- Frontend analytics.js: random localStorage visitor token; track() via sendBeacon;
  visit once/day; open on article click; share_ub/copy_source/native_share from the
  share menu; /a landing pages fire source_click. 127 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:21:49 +00:00

54 lines
1.9 KiB
Python

import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def app_db(tmp_path, monkeypatch):
db = tmp_path / "t.sqlite3"
monkeypatch.setenv("GOODNEWS_DB", str(db))
monkeypatch.setenv("GOODNEWS_SESSION_SECRET", "test-secret")
import importlib
import goodnews.api as api
importlib.reload(api)
from goodnews.db import connect, init_db
connect(str(db)).close() # creates schema lazily? ensure init
c = connect(str(db)); init_db(c); c.close()
return api.create_app(), db
def _count(db, **where):
from goodnews.db import connect
c = connect(str(db))
clause = " AND ".join(f"{k}=?" for k in where)
sql = "SELECT COUNT(*) FROM events" + (f" WHERE {clause}" if where else "")
n = c.execute(sql, tuple(where.values())).fetchone()[0]
c.close()
return n
def test_event_recorded_and_deduped(app_db):
app, db = app_db
tc = TestClient(app)
for _ in range(3): # same (kind, article, visitor, day) → one row
assert tc.post("/api/events", json={"kind": "open", "article_id": 5, "visitor": "tok"}).json() == {"ok": True}
assert _count(db, kind="open", article_id=5) == 1
# a different visitor is a distinct row
tc.post("/api/events", json={"kind": "open", "article_id": 5, "visitor": "other"})
assert _count(db, kind="open", article_id=5) == 2
def test_visitor_token_is_hashed_not_stored_raw(app_db):
app, db = app_db
TestClient(app).post("/api/events", json={"kind": "visit", "visitor": "secret-token"})
from goodnews.db import connect
c = connect(str(db))
vh = c.execute("SELECT visitor_hash FROM events").fetchone()[0]
c.close()
assert vh and vh != "secret-token" and len(vh) == 64 # sha256 hex
def test_unknown_kind_is_ignored(app_db):
app, db = app_db
assert TestClient(app).post("/api/events", json={"kind": "evil", "visitor": "x"}).json() == {"ok": True}
assert _count(db) == 0