Admin: Daily Word pool curation (lookup + add/remove)
First games admin tool. A "Games" tab in the operator console for the Daily Word answer pool. * Lookup: is a word real (in the guess dictionary), the right length (5/6), and already in the pool — instant as you type. * Add: appends to the pool, enforcing the invariant (alpha · 5/6 letters · in the guess dict) so the daily answer is always guessable. Remove: drops admin-added words (curated static ones stay). * Additions persist in a new word_pool table (survives redeploys, unlike the baked-in JSON); the daily picker reads static pool ∪ DB additions. Guess dicts shipped with the package (goodnews/data/words-5/6.json) for server-side validation. Admin-gated endpoints + tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -368,3 +368,25 @@ def test_wordsearch_thin_llm_falls_back(tmp_path, monkeypatch):
|
||||
rich = FakeClient("THEME: Rich Theme\nWORDS: " + ", ".join(rich_words))
|
||||
p2 = games.generate_wordsearch_puzzle(c, "2026-07-02", client=rich)
|
||||
assert p2["theme"] == "Rich Theme" and len(p2["words"]) >= games.WS_MIN_ACCEPT
|
||||
|
||||
|
||||
def test_word_pool_admin(tmp_path, monkeypatch):
|
||||
app, api = _make(tmp_path, monkeypatch, admin_email="boss@x.com")
|
||||
assert TestClient(app).get("/api/admin/word/pool").status_code == 401 # gated
|
||||
tc = _signin(app, api, "boss@x.com")
|
||||
# lookup: valid dict word vs non-word vs wrong length
|
||||
assert tc.get("/api/admin/word/lookup?word=thrive").json() == {
|
||||
"word": "thrive", "length": 6, "alpha": True, "variant": "6", "in_dictionary": True, "in_pool": True}
|
||||
assert tc.get("/api/admin/word/lookup?word=zzzzz").json()["in_dictionary"] is False
|
||||
assert tc.get("/api/admin/word/lookup?word=cat").json()["variant"] is None
|
||||
# add a valid word, then it shows in the pool + lookup
|
||||
res = tc.post("/api/admin/word/pool", json={"word": "PLUMB"}).json()
|
||||
assert res["added"] is True and "plumb" in res["pool"]["5"]["added"]
|
||||
assert tc.get("/api/admin/word/lookup?word=plumb").json()["in_pool"] is True
|
||||
# rejections: non-dictionary word + wrong length + duplicate
|
||||
assert tc.post("/api/admin/word/pool", json={"word": "qwxzv"}).status_code == 400
|
||||
assert tc.post("/api/admin/word/pool", json={"word": "cat"}).status_code == 400
|
||||
assert tc.post("/api/admin/word/pool", json={"word": "plumb"}).status_code == 400
|
||||
# remove the admin-added word
|
||||
tc.delete("/api/admin/word/pool/plumb")
|
||||
assert "plumb" not in tc.get("/api/admin/word/pool").json()["5"]["added"]
|
||||
|
||||
Reference in New Issue
Block a user