summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-11 23:02:32 -0500
committerChristian Cleberg <[email protected]>2026-04-11 23:02:32 -0500
commitdaaeba8be8df4d0876312e540bdd420e16ff724c (patch)
treef0261ab3959dc4863c027eb5c858c8f1fdda8d88
parent1c8d0bdd0a0a46d7400b0827aec88a787f5a04c3 (diff)
downloadhutch-stats-daaeba8be8df4d0876312e540bdd420e16ff724c.tar.gz
hutch-stats-daaeba8be8df4d0876312e540bdd420e16ff724c.tar.bz2
hutch-stats-daaeba8be8df4d0876312e540bdd420e16ff724c.zip
validate scraped usernames before enqueueing actors
-rw-r--r--src/srht_contrib/scripts/enqueue_actors.py27
-rw-r--r--tests/test_ingestion.py24
2 files changed, 44 insertions, 7 deletions
diff --git a/src/srht_contrib/scripts/enqueue_actors.py b/src/srht_contrib/scripts/enqueue_actors.py
index f6999af..03f1f9b 100644
--- a/src/srht_contrib/scripts/enqueue_actors.py
+++ b/src/srht_contrib/scripts/enqueue_actors.py
@@ -3,6 +3,7 @@ from __future__ import annotations
import argparse
from datetime import UTC, datetime, timedelta
from pathlib import Path
+import re
from sqlalchemy import select
@@ -11,19 +12,31 @@ from srht_contrib.db import make_session_factory
from srht_contrib.models import TrackedActor
+USERNAME_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,61}[A-Za-z0-9])?$")
+
+
+def _normalize_actor(raw_username: str) -> str | None:
+ username = raw_username.strip()
+ if not username or username.startswith("#"):
+ return None
+ if username.startswith("~"):
+ username = username[1:]
+ if not USERNAME_RE.fullmatch(username):
+ return None
+ return f"~{username}"
+
+
def _iter_usernames(path: Path) -> list[str]:
usernames: list[str] = []
seen: set[str] = set()
for raw_line in path.read_text(encoding="utf-8").splitlines():
- username = raw_line.strip()
- if not username or username.startswith("#"):
+ actor = _normalize_actor(raw_line)
+ if actor is None:
continue
- if not username.startswith("~"):
- username = f"~{username}"
- if username in seen:
+ if actor in seen:
continue
- seen.add(username)
- usernames.append(username)
+ seen.add(actor)
+ usernames.append(actor)
return usernames
diff --git a/tests/test_ingestion.py b/tests/test_ingestion.py
index 974dac6..6a1a162 100644
--- a/tests/test_ingestion.py
+++ b/tests/test_ingestion.py
@@ -738,3 +738,27 @@ def test_enqueue_actors_staggers_without_polling(tmp_path, monkeypatch) -> None:
assert actors[0].next_poll_after == queued_at.replace(tzinfo=None)
assert actors[1].next_poll_after == (queued_at + timedelta(seconds=60)).replace(tzinfo=None)
assert actors[2].next_poll_after == (queued_at + timedelta(seconds=120)).replace(tzinfo=None)
+
+
+def test_enqueue_actors_skips_invalid_usernames(tmp_path, monkeypatch) -> None:
+ database_path = tmp_path / "enqueue-invalid.db"
+ username_path = tmp_path / "srht_usernames.txt"
+ username_path.write_text("-0\n.\n~bad-\nvalid_user\nok.ok\n", encoding="utf-8")
+ monkeypatch.setenv("DATABASE_URL", f"sqlite:///{database_path}")
+ monkeypatch.setenv("SRHT_TOKEN", "test-token")
+ monkeypatch.setenv("DEFAULT_ACTOR", "~ccleberg")
+
+ from srht_contrib.db import Base, make_engine, make_session_factory
+
+ settings = Settings()
+ engine = make_engine(settings)
+ Base.metadata.create_all(bind=engine)
+ session_factory = make_session_factory(settings)
+
+ inserted = enqueue_actors(Path(username_path), stagger_seconds=60)
+
+ with session_factory() as db:
+ actors = db.scalars(select(TrackedActor).order_by(TrackedActor.actor)).all()
+
+ assert inserted == 2
+ assert [actor.actor for actor in actors] == ["~ok.ok", "~valid_user"]