feat(hh): /pw command, RAM-only direnv autostart, robust lets-hack; coven→clergy

- add /pw (alias /password): reveal this room's password locally (never
  broadcast); surfaced in the F1 help overlay and the join hint
- direnv-autostart/: cd-to-launch a single real-user session via direnv;
  password is minted in memory at launch (never written to disk, matching the
  RAM-only model) and scoped to the child process. setup.sh installs direnv,
  hooks bash/zsh, and `direnv allow`s the dir
- lets-hack.sh: boot a FRESH server by default (replacing any live one) with a
  --reuse opt-out; add -h/--help/-help; guard against killing the tmux session
  you're attached to; switch-client into the coven when run inside tmux
- rename coven→clergy across rust/python/scripts; tests/test_coven.py→test_clergy.py
- snapshots in-progress hack-house client work (sandbox, themes, net, ui)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-31 22:29:17 -07:00
parent 8e6365a649
commit 5de493e895
21 changed files with 1058 additions and 91 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Use-case tests for the hack-house multi-user clergy features (capacity cap,
roster, username + slot lifecycle). In-process via sanic-testing.
"""
import base64
import json
import srp
from cmd_chat.server.models import UserSession
from cmd_chat.server.views import _roster_frame
def _add_member(app, name):
"""Seat a verified member directly in the session store."""
app.ctx.session_store.add(
UserSession(user_id=f"id-{name}", ip="127.0.0.1", username=name)
)
def _init(test_client, name):
"""Run /srp/init for a fresh username (just the first handshake leg)."""
usr = srp.User(b"chat", b"testpassword")
_, A = usr.start_authentication()
return test_client.post(
"/srp/init",
json={"username": name, "A": base64.b64encode(A).decode()},
)
class TestClergyCapacity:
def test_accepts_up_to_capacity(self, app, test_client):
app.ctx.max_users = 4
for n in ("a", "b", "c"):
_add_member(app, n)
# 4th seat is still available
_, resp = _init(test_client, "fourth")
assert resp.status == 200
def test_rejects_when_full(self, app, test_client):
app.ctx.max_users = 4
for n in ("a", "b", "c", "d"):
_add_member(app, n)
_, resp = _init(test_client, "fifth")
assert resp.status == 409
assert "full" in resp.json["error"].lower()
def test_capacity_is_configurable(self, app, test_client):
app.ctx.max_users = 2
for n in ("a", "b"):
_add_member(app, n)
_, resp = _init(test_client, "third")
assert resp.status == 409
class TestUsernames:
def test_duplicate_username_rejected(self, app, test_client):
_add_member(app, "ghost")
_, resp = _init(test_client, "ghost")
assert resp.status == 409
assert "taken" in resp.json["error"].lower()
class TestRoster:
def test_roster_frame_contents(self, app):
_add_member(app, "alice")
_add_member(app, "bob")
frame = json.loads(_roster_frame(app))
assert frame["type"] == "roster"
assert frame["capacity"] == app.ctx.max_users
assert {u["username"] for u in frame["users"]} == {"alice", "bob"}
assert all("user_id" in u for u in frame["users"])
class TestSlotLifecycle:
def test_disconnect_frees_slot_and_username(self, app, test_client):
app.ctx.max_users = 1
_add_member(app, "ghost")
assert app.ctx.session_store.username_exists("ghost")
# full room rejects a newcomer
_, resp = _init(test_client, "intruder")
assert resp.status == 409
# the disconnect path removes the session (frees slot + name)
app.ctx.session_store.remove("id-ghost")
assert not app.ctx.session_store.username_exists("ghost")
assert app.ctx.session_store.count() == 0
# name is reusable and the slot is open again
_, resp = _init(test_client, "ghost")
assert resp.status == 200