test: add 11 new tests for stats, go, and emote commands

- TestStatsEndpoint: empty bridge stats, stats after activity
- TestGoCommand: room change, occupant tracking, old-room notification,
  same-room rejection, missing room argument
- TestEmoteCommand: room broadcast, first-person response, no self-echo,
  /me alias support
- Also fix bare 'go'/'move' (no argument) to show usage hint
This commit is contained in:
Alexander Whitestone
2026-04-13 15:09:52 -04:00
parent bb21d6c790
commit 623e397d68
2 changed files with 182 additions and 1 deletions

View File

@@ -532,7 +532,7 @@ class MultiUserBridge:
other_session.room_events.append(broadcast)
return f'You say: \"{speech}\"'
if msg_lower.startswith("go ") or msg_lower.startswith("move "):
if msg_lower.startswith("go ") or msg_lower.startswith("move ") or msg_lower == "go" or msg_lower == "move":
# Move to a new room (HTTP equivalent of WS move)
parts = message.split(None, 1)
if len(parts) < 2 or not parts[1].strip():

View File

@@ -480,3 +480,184 @@ class TestRateLimitingHTTP:
"user_id": "bob", "message": "im fine",
})
assert resp2.status == 200
# ── Stats Endpoint Tests ─────────────────────────────────────
class TestStatsEndpoint:
@pytest.mark.asyncio
async def test_stats_empty_bridge(self, client):
c, _ = client
resp = await c.get("/bridge/stats")
assert resp.status == 200
data = await resp.json()
assert data["active_sessions"] == 0
assert data["total_messages"] == 0
assert data["total_commands"] == 0
assert data["room_count"] == 0
assert data["ws_connections"] == 0
assert "uptime_seconds" in data
@pytest.mark.asyncio
async def test_stats_after_activity(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "bob", "message": "hey", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "look", "room": "Tower"
})
resp = await c.get("/bridge/stats")
data = await resp.json()
assert data["active_sessions"] == 2
assert data["total_messages"] == 6 # 3 chats × 2 (user + assistant) = 6
assert data["room_count"] == 1
assert "Tower" in data["rooms"]
# ── Go Command Tests ─────────────────────────────────────────
class TestGoCommand:
@pytest.mark.asyncio
async def test_go_changes_room(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
resp = await c.post("/bridge/chat", json={
"user_id": "alice", "message": "go Chapel", "room": "Tower"
})
data = await resp.json()
assert "Chapel" in data["response"]
assert data["room"] == "Chapel"
@pytest.mark.asyncio
async def test_go_updates_room_occupants(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "bob", "message": "hi", "room": "Tower"
})
# Alice moves to Chapel
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "go Chapel", "room": "Tower"
})
# Tower should only have bob
resp = await c.get("/bridge/rooms")
data = await resp.json()
tower_users = {o["user_id"] for o in data["rooms"]["Tower"]["occupants"]}
assert tower_users == {"bob"}
@pytest.mark.asyncio
async def test_go_notifies_old_room(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "bob", "username": "Bob", "message": "hi", "room": "Tower"
})
# Alice leaves Tower
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "go Chapel", "room": "Tower"
})
# Bob should get a room event about Alice leaving
resp = await c.get("/bridge/room_events/bob")
data = await resp.json()
assert data["count"] >= 1
assert any("Alice" in e.get("message", "") and "Chapel" in e.get("message", "") for e in data["events"])
@pytest.mark.asyncio
async def test_go_same_room_rejected(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
resp = await c.post("/bridge/chat", json={
"user_id": "alice", "message": "go Tower", "room": "Tower"
})
data = await resp.json()
assert "already" in data["response"].lower()
@pytest.mark.asyncio
async def test_go_no_room_given(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
resp = await c.post("/bridge/chat", json={
"user_id": "alice", "message": "go", "room": "Tower"
})
data = await resp.json()
assert "usage" in data["response"].lower()
# ── Emote Command Tests ──────────────────────────────────────
class TestEmoteCommand:
@pytest.mark.asyncio
async def test_emote_broadcasts_to_room(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "bob", "username": "Bob", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "emote waves hello", "room": "Tower"
})
resp = await c.get("/bridge/room_events/bob")
data = await resp.json()
assert data["count"] >= 1
assert any("Alice waves hello" in e.get("message", "") for e in data["events"])
@pytest.mark.asyncio
async def test_emote_returns_first_person(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
resp = await c.post("/bridge/chat", json={
"user_id": "alice", "message": "emote dances wildly", "room": "Tower"
})
data = await resp.json()
assert "dances wildly" in data["response"]
assert "Alice" not in data["response"] # first person, no username
@pytest.mark.asyncio
async def test_emote_no_echo_to_self(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "alice", "message": "emote sits down", "room": "Tower"
})
resp = await c.get("/bridge/room_events/alice")
data = await resp.json()
assert data["count"] == 0
@pytest.mark.asyncio
async def test_slash_me_alias(self, client):
c, _ = client
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "bob", "username": "Bob", "message": "hi", "room": "Tower"
})
await c.post("/bridge/chat", json={
"user_id": "alice", "username": "Alice", "message": "/me stretches", "room": "Tower"
})
resp = await c.get("/bridge/room_events/bob")
data = await resp.json()
assert any("Alice stretches" in e.get("message", "") for e in data["events"])