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:
@@ -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():
|
||||
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user