Private
Public Access
Phase 1: auth, room CRUD, WebSocket chat, PWA frontend
Invite-only FastAPI + SQLAlchemy(async) + Postgres backend (session-cookie auth via CLI-provisioned accounts, open-room CRUD, single-instance /ws/chat) and a React + Vite PWA frontend (login, room list, chat view). Backend tests pass against a local Postgres DB. See README.md and backend/README.md for setup, and ARCHITECTURE.md for the full phased design. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import uuid
|
||||
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
|
||||
from app.schemas.user import UserCreate
|
||||
from app.services.auth_service import register_user
|
||||
from app.ws.chat import WS_UNAUTHENTICATED
|
||||
|
||||
|
||||
def _unique(prefix: str) -> str:
|
||||
return f"{prefix}-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
def _register(ws_client, username):
|
||||
# No public register endpoint (invite-only site): seed the user directly
|
||||
# via the ws_client's own session factory (see conftest.ws_client), then
|
||||
# log in through the real endpoint to get a session cookie.
|
||||
async def _seed():
|
||||
async with ws_client.session_factory() as session:
|
||||
await register_user(
|
||||
session,
|
||||
UserCreate(username=username, email=f"{username}@example.com", password="password123"),
|
||||
)
|
||||
|
||||
ws_client.portal.call(_seed)
|
||||
|
||||
resp = ws_client.post(
|
||||
"/api/auth/login",
|
||||
json={"username_or_email": username, "password": "password123"},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()
|
||||
|
||||
|
||||
def test_ws_requires_auth(ws_client):
|
||||
try:
|
||||
with ws_client.websocket_connect("/ws/chat"):
|
||||
pass
|
||||
assert False, "expected the connection to be rejected"
|
||||
except WebSocketDisconnect as exc:
|
||||
assert exc.code == WS_UNAUTHENTICATED
|
||||
|
||||
|
||||
def test_ws_join_and_message_roundtrip(ws_client):
|
||||
username = _unique("alice")
|
||||
_register(ws_client, username=username)
|
||||
room = ws_client.post("/api/rooms", json={"name": _unique("general")}).json()
|
||||
|
||||
with ws_client.websocket_connect("/ws/chat") as ws:
|
||||
ws.send_json({"type": "join", "room_id": room["id"]})
|
||||
joined = ws.receive_json()
|
||||
assert joined == {"type": "joined", "room_id": room["id"]}
|
||||
|
||||
ws.send_json({"type": "message", "room_id": room["id"], "content": "hello"})
|
||||
message = ws.receive_json()
|
||||
assert message["type"] == "message"
|
||||
assert message["content"] == "hello"
|
||||
assert message["room_id"] == room["id"]
|
||||
assert message["username"] == username
|
||||
|
||||
resp = ws_client.get(f"/api/rooms/{room['id']}/messages")
|
||||
assert resp.status_code == 200
|
||||
contents = [m["content"] for m in resp.json()]
|
||||
assert "hello" in contents
|
||||
|
||||
|
||||
def test_ws_message_without_join_errors(ws_client):
|
||||
_register(ws_client, username=_unique("alice"))
|
||||
room = ws_client.post("/api/rooms", json={"name": _unique("general")}).json()
|
||||
|
||||
with ws_client.websocket_connect("/ws/chat") as ws:
|
||||
ws.send_json({"type": "message", "room_id": room["id"], "content": "hello"})
|
||||
resp = ws.receive_json()
|
||||
assert resp["type"] == "error"
|
||||
Reference in New Issue
Block a user