This commit is contained in:
Kilton937342 2022-09-21 22:31:50 +02:00
parent 84b38cc12d
commit a26b4aaa43
7 changed files with 871 additions and 104 deletions

7
backend/api/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

View File

@ -9,19 +9,20 @@ def create_room_db(*,room: RoomCreate, user: User | None = None, username: str |
id_code = generate_unique_code(Room,s=db) id_code = generate_unique_code(Room,s=db)
room_obj = Room(**room.dict(exclude_unset=True), id_code=id_code) room_obj = Room(**room.dict(exclude_unset=True), id_code=id_code)
if user is not None: if user is not None:
member = Member(user_id=user.id, room=room_obj) member = Member(user_id=user.id, room=room_obj, is_admin=True)
db.add(member) db.add(member)
db.commit() db.commit()
db.refresh(member) db.refresh(member)
if username is not None: if username is not None:
reconnect_code = generate_unique_code(Anonymous, s=db, field_name='reconnect_code') reconnect_code = generate_unique_code(Anonymous, s=db, field_name='reconnect_code')
anonymous = Anonymous(username=username, reconnect_code=reconnect_code) anonymous = Anonymous(username=username, reconnect_code=reconnect_code)
member = Member(anonymous=anonymous, room=room_obj) member = Member(anonymous=anonymous, room=room_obj, is_admin=True)
db.add(member) db.add(member)
db.commit() db.commit()
db.refresh(member) db.refresh(member)
if username is None and user is None: if username is None and user is None:
raise ValueError('Username or user required') raise ValueError('Username or user required')
return {"room": room_obj, "member": member} return {"room": room_obj, "member": member}
def check_room(room_id: str, db: Session = Depends(get_session)): def check_room(room_id: str, db: Session = Depends(get_session)):

View File

@ -1,3 +1,4 @@
from pydantic import root_validator
from typing import List, Optional, TYPE_CHECKING from typing import List, Optional, TYPE_CHECKING
from sqlmodel import SQLModel, Field, Relationship from sqlmodel import SQLModel, Field, Relationship
@ -48,18 +49,43 @@ class Member(SQLModel, table = True):
is_admin: bool = False is_admin: bool = False
waiting: bool = True waiting: bool = False
online: bool = False online: bool = False
waiter_code: Optional[str] = Field(default= None)
class RoomRead(RoomBase): class RoomRead(RoomBase):
id_code: str id_code: str
#members: List['Member'] #members: List['Member']
class AnonymousRead(AnonymousBase): class AnonymousRead(AnonymousBase):
reconnect_code: str reconnect_code: str
class Username(SQLModel): class Username(SQLModel):
username: str username: str
class MemberWithRelations(SQLModel):
is_admin: bool
user: UserRead | None = None
anonymous: AnonymousRead | None = None
class MemberRead(SQLModel): class MemberRead(SQLModel):
anonymous: AnonymousRead = None username: str
user: Username = None reconnect_code: str = ''
isUser: bool
isAdmin: bool
class MemberSerializer(MemberRead):
member: MemberWithRelations
@root_validator
def parse_member(cls, values):
member = values.get('member')
if member == None:
return values
member_obj = member.user or member.anonymous
if member_obj is None:
raise ValueError('User or anonymous required')
return {"username": member_obj.username, "reconnect_code": getattr(member_obj, "reconnect_code", ""), "isAdmin": member.is_admin, "isUser": member.user != None}

View File

@ -7,7 +7,7 @@ from services.auth import get_current_user_optional, jwt_required
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from database.auth.models import User, UserRead from database.auth.models import User, UserRead
from database.exercices.models import Exercice, ExerciceRead from database.exercices.models import Exercice, ExerciceRead
from database.room.models import Room, Anonymous, Member from database.room.models import Room, Anonymous, MemberWithRelations
import database.db import database.db
from fastapi_pagination import add_pagination from fastapi_pagination import add_pagination
from fastapi.responses import PlainTextResponse from fastapi.responses import PlainTextResponse

View File

@ -1,3 +1,9 @@
from pydantic import Field
from pydantic import create_model_from_typeddict
from pydantic.error_wrappers import ValidationError
from pydantic import validate_arguments
from services.database import generate_unique_code
from sqlmodel import col
from sqlmodel import select from sqlmodel import select
from pydantic import BaseModel from pydantic import BaseModel
from typing import Any, Callable, Dict, List, Optional from typing import Any, Callable, Dict, List, Optional
@ -8,10 +14,11 @@ from database.auth.models import User
from database.db import get_session from database.db import get_session
from database.room.crud import check_room, create_room_db, userInRoom from database.room.crud import check_room, create_room_db, userInRoom
from sqlmodel import Session from sqlmodel import Session
from database.room.models import Anonymous, Member, MemberRead, Room, RoomCreate, RoomRead from database.room.models import Anonymous, MemberSerializer, MemberWithRelations, MemberRead, Room, RoomCreate, RoomRead, Member
from services.auth import get_current_user_optional from services.auth import get_current_user_optional
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from jose import jwt, exceptions from jose import jwt, exceptions
import inspect
router = APIRouter(tags=["room"]) router = APIRouter(tags=["room"])
@ -20,17 +27,30 @@ class RoomAndMember(BaseModel):
member: MemberRead member: MemberRead
class Waiter(BaseModel):
username: str
waiter_id: str
def serialize_member(member: Member) -> MemberRead | Waiter:
member_obj = member.user or member.anonymous
if member.waiting == False:
return MemberRead(username=member_obj.username, reconnect_code=getattr(member_obj, "reconnect_code", ""), isUser=member.user_id != None, isAdmin=member.is_admin)
if member.waiting == True:
return Waiter(username=member_obj.username, waiter_id=member.waiter_code)
@router.post('/room', response_model=RoomAndMember) @router.post('/room', response_model=RoomAndMember)
def create_room(room: RoomCreate, username: Optional[str] = Query(default=None, max_length=20), user: User | None = Depends(get_current_user_optional), db: Session = Depends(get_session)): def create_room(room: RoomCreate, username: Optional[str] = Query(default=None, max_length=20), user: User | None = Depends(get_current_user_optional), db: Session = Depends(get_session)):
room_obj = create_room_db(room=room, user=user, username=username, db=db) room_obj = create_room_db(room=room, user=user, username=username, db=db)
return room_obj return {'room': room_obj['room'], "member": serialize_member(room_obj['member'])}
class ConnectionManager: class ConnectionManager:
def __init__(self): def __init__(self):
self.active_connections: Dict[str, List[WebSocket]] = {} self.active_connections: Dict[str, List[WebSocket]] = {}
async def add(self, group: str, ws: WebSocket): def add(self, group: str, ws: WebSocket):
if group not in self.active_connections: if group not in self.active_connections:
self.active_connections[group] = [] self.active_connections[group] = []
@ -49,22 +69,54 @@ class ConnectionManager:
await connection.send_json(message) await connection.send_json(message)
manager = ConnectionManager()
def make_event_decorator(eventsDict): def make_event_decorator(eventsDict):
def _(name: str): def _(name: str, conditions: List[Callable | bool] = []):
def add_event(func): def add_event(func):
eventsDict[name] = func model = validate_arguments(func).model
eventsDict[name] = {"func": func,
"conditions": conditions, "model": model}
return func return func
return add_event return add_event
return _ return _
class Event(BaseModel):
func: Callable
conditions: List[Callable | bool]
model: BaseModel
def dict_model(model: BaseModel, exclude: List[str]):
value = {}
for n, f in model:
if n not in exclude:
value[n] = f
return value
def dict_all(obj: Any):
if isinstance(obj, dict):
value = {}
for k, v in obj.items():
if isinstance(v, dict):
v = dict_all(v)
value[k] = dict(v)
elif isinstance(v, BaseModel):
value[k] = dict(v)
else:
try:
value[k] = dict(v)
except:
value[k] = v
return value
return dict(obj)
class Consumer: class Consumer:
events: Dict[str, Callable] = {} events: Dict[str, Event] = {}
sendings: Dict[str, Any] = {}
event = make_event_decorator(events) event = make_event_decorator(events)
sending = make_event_decorator(sendings)
def __init__(self, ws: WebSocket): def __init__(self, ws: WebSocket):
self.ws: WebSocket = ws self.ws: WebSocket = ws
@ -73,13 +125,70 @@ class Consumer:
async def connect(self): async def connect(self):
pass pass
async def validation_error_handler(self, e: ValidationError):
errors = e.errors()
await self.ws.send_json({"type": "error", "data": {"detail": [{ers['loc'][-1]: ers['msg']} for ers in errors]}})
async def send(self, payload):
type = payload.get('type', None)
print('TYPE', type, self.member.id)
if type is not None:
event_wrapper = self.sendings.get(type, None)
if event_wrapper is not None:
handler = event_wrapper.get('func')
conditions = event_wrapper.get('conditions')
is_valid = all([(await c(self)) if inspect.iscoroutinefunction(c) else c(self) if inspect.isfunction(c) else c == True if isinstance(c, bool) else True for c in conditions])
if handler is not None and is_valid:
model = event_wrapper.get("model")
data = payload.get('data') or {}
try:
validated_payload = model(self=self, **data)
except ValidationError as e:
await self.ws.send_json({"type": "error", "data": {"msg": "Oops there was an error"}})
return
validated_payload = dict_model(validated_payload,
exclude=["v__duplicate_kwargs", "args", 'kwargs', "self"])
try:
parsed_payload = handler(
self, **validated_payload)
await self.ws.send_json({'type': type, "data": dict_all(parsed_payload)})
return
except Exception as e:
print('NOPE', self.member.id, e)
return
return
print('pls')
await self.ws.send_json(payload)
print('sent')
async def receive(self, data): async def receive(self, data):
event = data.get('type', None) event = data.get('type', None)
if event is not None: if event is not None:
handler = self.events.get(event, None) event_wrapper = self.events.get(event, None)
if handler is not None: if event_wrapper is not None:
payload = data.get('data') handler = event_wrapper.get('func')
await handler(self, payload) conditions = event_wrapper.get('conditions')
is_valid = all([(await c(self)) if inspect.iscoroutinefunction(c) else c(self) if inspect.isfunction(c) else c == True if isinstance(c, bool) else True for c in conditions])
if handler is not None and is_valid:
model = event_wrapper.get("model")
payload = data.get('data') or {}
try:
validated_payload = model(self=self, **payload)
except ValidationError as e:
await self.validation_error_handler(e)
return
await handler(**{k: v for k, v in validated_payload.dict().items() if k not in ["v__duplicate_kwargs", "args", 'kwargs']})
async def disconnect(self): async def disconnect(self):
pass pass
@ -94,6 +203,36 @@ class Consumer:
await self.disconnect() await self.disconnect()
class ConsumerManager:
def __init__(self):
self.active_connections: Dict[str, List[Consumer]] = {}
def add(self, group: str, ws: Consumer):
if group not in self.active_connections:
self.active_connections[group] = []
print("adding", ws, self.active_connections[group])
if ws not in self.active_connections[group]:
print('ACTUALLY ADDING')
self.active_connections[group].append(ws)
def remove(self, group: str, ws: Consumer):
if group in self.active_connections:
if ws in self.active_connections[group]:
self.active_connections[group].remove(ws)
async def broadcast(self, message, group: str, exclude: list[Consumer] = []):
if group in self.active_connections:
print(self.active_connections[group], exclude)
for connection in list(set(self.active_connections[group])):
if connection not in exclude:
print('SEND TO', connection, message)
await connection.send(message)
manager = ConsumerManager()
class Token(BaseModel): class Token(BaseModel):
token: str token: str
@ -115,12 +254,30 @@ def get_member_from_user(user_id: int, room_id: int, db: Session):
return member return member
def get_member_from_token(token: str, room_id: int, db: Session):
user = get_user_from_token(token, db)
if user is False:
return False
if user is None:
return None
member = get_member_from_user(user.id, room_id, db)
return member
def get_member_from_anonymous(anonymous_id: int, room_id: int, db: Session): def get_member_from_anonymous(anonymous_id: int, room_id: int, db: Session):
member = db.exec(select(Member).where(Member.room_id == member = db.exec(select(Member).where(Member.room_id ==
room_id, Member.anonymous_id == anonymous_id)).first() room_id, Member.anonymous_id == anonymous_id)).first()
return member return member
def get_member_reconnect_code(reconnect_code: str, room_id: int, db: Session):
anonymous = get_anonymous_from_code(reconnect_code, db)
if anonymous is None:
return None
member = get_member_from_anonymous(anonymous.id, room_id, db)
return member
def get_anonymous_from_code(reconnect_code: str, db: Session): def get_anonymous_from_code(reconnect_code: str, db: Session):
anonymous = db.exec(select(Anonymous).where( anonymous = db.exec(select(Anonymous).where(
Anonymous.reconnect_code == reconnect_code)).first() Anonymous.reconnect_code == reconnect_code)).first()
@ -136,31 +293,117 @@ def connect_member(member: Member, db: Session):
def disconnect_member(member: Member, db: Session): def disconnect_member(member: Member, db: Session):
member.online = False if member.waiting == False:
member.online = False
db.add(member)
db.commit()
db.refresh(member)
return member
else:
db.delete(member)
db.commit()
return member
def validate_username(username: str, room: Room, db: Session = Depends(get_session)):
print('VALIDATE', username)
if len(username) > 20:
return None
members = select(Member.anonymous_id).where(
Member.room_id == room.id, Member.anonymous_id != None)
anonymous = select(Anonymous).where(
col(Anonymous.id).in_(members), Anonymous.username == username)
username_anonymous = db.exec(anonymous).first()
return None if username_anonymous is not None else username
def create_anonymous_member(username: str, room: Room, db: Session):
username = validate_username(username)
if username is None:
return None
reconnect_code = generate_unique_code(
Anonymous, s=db, field_name="reconnect_code")
anonymous = Anonymous(username=username, reconnect_code=reconnect_code)
member = Member(room=room, anonymous=anonymous)
db.add(member) db.add(member)
db.commit() db.commit()
db.refresh(member) db.refresh(member)
return member return member
def validate_username(username: str, room: Room, db: Session): def create_user_member(user: User, room: Room, db: Session):
members = select(Member).where(Member.room_id == room.id, Member.anonymous_id != None) member = get_member_from_user(user.id, room.id, db)
if member is not None:
def create_anonymous_member(username: str, room: Room, db: Session): return None
pass member = Member(room=room, user=user)
db.add(member)
db.commit()
db.refresh(member)
return member
def create_anonymous_waiter(username: str, room: Room, db: Session):
username = validate_username(username, room, db)
if username is None:
return None
reconnect_code = generate_unique_code(
Anonymous, s=db, field_name="reconnect_code")
anonymous = Anonymous(username=username, reconnect_code=reconnect_code)
waiter_code = generate_unique_code(Member, s=db, field_name="waiter_code")
member = Member(room=room, anonymous=anonymous,
waiting=True, waiter_code=waiter_code)
db.add(member)
db.commit()
db.refresh(member)
return member
def create_user_waiter(user: User, room: Room, db: Session):
member = get_member_from_user(user.id, room.id, db)
if member is not None:
return None
waiter_code = generate_unique_code(Member, s=db, field_name="waiter_code")
member = Member(room=room, user=user, waiting=True,
waiter_code=waiter_code)
db.add(member)
db.commit()
db.refresh(member)
return member
def get_waiter(waiter_code: str, db: Session):
return db.exec(select(Member).where(Member.waiter_code == waiter_code)).first()
def accept_waiter(member: Member, db: Session):
member.waiting = False
member.waiter_code = None
db.add(member)
db.commit()
db.refresh(member)
return member
def refuse_waiter(member: Member, db: Session):
db.delete(member)
db.commit()
return None
class RoomConsumer(Consumer): class RoomConsumer(Consumer):
def __init__(self, ws: WebSocket, room: Room, manager: ConnectionManager, db: Session):
def __init__(self, ws: WebSocket, room: Room, manager: ConsumerManager, db: Session):
self.room = room self.room = room
self.ws = ws self.ws = ws
self.manager = manager self.manager = manager
self.db = db self.db = db
self.member = None self.member = None
#WS Utilities # WS Utilities
async def connect(self): async def connect(self):
await self.ws.accept() await self.ws.accept()
async def send(self, type: str, payload: Any): async def direct_send(self, type: str, payload: Any):
await self.ws.send_json({'type': type, "data": payload}) await self.ws.send_json({'type': type, "data": payload})
async def send_to_all_room(self, type: str, payload: Any, exclude: bool = False): async def send_to_all_room(self, type: str, payload: Any, exclude: bool = False):
@ -173,81 +416,170 @@ class RoomConsumer(Consumer):
async def send_to_members(self, type: str, payload: Any, exclude: bool = False): async def send_to_members(self, type: str, payload: Any, exclude: bool = False):
await self.manager.broadcast({'type': type, "data": payload}, f'{self.room.id}__member', [exclude == True and self.ws]) await self.manager.broadcast({'type': type, "data": payload}, f'{self.room.id}__member', [exclude == True and self.ws])
async def broadcast(self, type, payload, exclude= False):
await self.manager.broadcast({"type": type, "data": payload}, self.room.id, exclude = [exclude == True and self])
def add_to_admin(self): def add_to_admin(self):
self.manager.add(f'{self.room.id}__admin', self.ws) self.manager.add(f'{self.room.id}__admin', self.ws)
def add_to_members(self): def add_to_members(self):
self.manager.add(f'{self.room.id}__members', self.ws) self.manager.add(f'{self.room.id}__members', self.ws)
def add_to_groups(self): def add_to_group(self):
if isinstance(self.member, Member): if self.member.waiting == True:
if self.member.is_admin == True: self.manager.add(f'waiter__{self.member.waiter_code}', self)
self.add_to_admin() self.manager.add(self.room.id, self)
if self.member.is_admin == False:
self.add_to_members()
async def connect_self(self): async def connect_self(self):
if isinstance(self.member, Member): if isinstance(self.member, Member):
connect_member(self.member, self.db) connect_member(self.member, self.db)
await self.send_to_all_room(type="connect", payload={}, exclude=True) await self.broadcast(type="connect", payload={"member": serialize_member(self.member).dict()})
async def disconnect_self(self): async def disconnect_self(self):
if isinstance(self.member, Member): if isinstance(self.member, Member):
disconnect_member(self.member, self.db) disconnect_member(self.member, self.db)
await self.send_to_all_room(type="disconnect", payload={}, exclude=True) await self.broadcast(type="disconnect", payload={"member": serialize_member(self.member).dict()})
#DB Utilities # DB Utilities
#Events # Received Events
@Consumer.event('login') @Consumer.event('login')
async def login(self, data): async def login(self, token: str | None = None, reconnect_code: str | None = None):
if 'token' in data: if token is not None:
token = data.get('token') member = get_member_from_token(token, self.room.id, self.db)
user = get_user_from_token(token, db=self.db) if member == False:
if user == False: await self.direct_send(type="error", payload={"msg": "Token expired"})
await self.send()
return return
if user is None:
return
member = get_member_from_user(
user_id=user.id, room_id=self.room.id, db=self.db)
if member is None: if member is None:
await self.direct_send(type="error", payload={"msg": "Utilisateur introuvable dans cette salle"})
return
self.member = member
# await self.connect_self()
self.add_to_group()
await self.direct_send(type="loggedIn", payload={"member": serialize_member(self.member).dict()})
elif reconnect_code is not None:
member = get_member_reconnect_code(
reconnect_code, self.room.id, db=self.db)
if member is None:
await self.direct_send(type="error", payload={"msg": "Utilisateur introuvable dans cette salle"})
return return
self.member = member self.member = member
self.add_to_groups()
self.connect_self()
await self.send()
elif "reconnect_code" in data: # await self.connect_self()
reconnect_code = data.get('reconnect_code') self.add_to_group()
anonymous = get_anonymous_from_code( await self.direct_send(type="loggedIn", payload={"member": serialize_member(self.member).dict()})
reconnect_code=reconnect_code, db=self.db) if reconnect_code is None and token is None:
if anonymous is None: await self.direct_send(type="error", payload={"msg": "Veuillez spécifier une méthode de connection"})
return
member = get_member_from_anonymous(
anonymous_id=anonymous.id, room_id=self.room.id, db=self.db)
if member is None:
return
self.member = member
self.add_to_groups()
self.connect_self()
await self.send(type="accepted")
@Consumer.event('join') @Consumer.event('join')
async def join(self, data): async def join(self, token: str | None = None, username: str | None = None):
if "token" in data: if self.room.public == False:
return if token is not None:
else: user = get_user_from_token(token, self.db)
return
return if user is None:
await self.direct_send(type="error", payload={"msg": "Utilisateur introuvable"})
return
if user is False:
await self.direct_send(type="error", payload={"msg": "Token expired"})
return
waiter = create_user_waiter(user, self.room, self.db)
if waiter.waiting is False:
self.member = waiter
# await self.connect_self()
self.add_to_group()
await self.direct_send(type="loggedIn", payload={"member": serialize_member(self.member).dict()})
return
self.member = waiter
self.add_to_group()
await self.direct_send(type="waiting", payload={"waiter": serialize_member(self.member).dict()})
await self.broadcast(type="waiter", payload={"waiter": serialize_member(self.member).dict()})
elif username is not None:
waiter = create_anonymous_waiter(username, self.room, self.db)
if waiter is None:
await self.direct_send(type="error", payload={"msg": "Nom d'utilisateur invalide ou indisponible"})
return
self.member = waiter
self.add_to_group()
await self.direct_send(type="waiting", payload={"waiter": serialize_member(self.member).dict()})
await self.broadcast(type="waiter", payload={"waiter": serialize_member(self.member).dict()})
else:
if token is not None:
user = get_user_from_token(token, self.db)
if user is None:
return
if user is False:
return
member = create_user_member(user, self.room, self.db)
if member is None:
return
self.member = member
self.add_to_group()
await self.direct_send()
elif username is not None:
member = create_anonymous_member(username, self.room, self.db)
if member is None:
await self.direct_send(type="error", data={"msg": "Nom d'utilisateur indisponible"})
return
self.member = member
self.add_to_group()
await self.direct_send()
def isAdmin(self):
return self.member is not None and self.member.is_admin == True
@Consumer.event('accept', conditions=[isAdmin])
async def accept(self, waiter_id: str):
waiter = get_waiter(waiter_id, self.db)
member = accept_waiter(waiter, self.db)
await self.manager.broadcast({"type": "accepted", "data": {'member': serialize_member(member).dict()}}, f"waiter__{waiter_id}")
await self.broadcast(type="joined", payload={"member": serialize_member(member).dict()})
@Consumer.event('refuse', conditions=[isAdmin])
async def accept(self, waiter_id: str):
waiter = get_waiter(waiter_id, self.db)
member = refuse_waiter(waiter, self.db)
await self.manager.broadcast({"type": "refused", "data": {'waiter_id': waiter_id}}, f"waiter__{waiter_id}")
#await self.broadcast(type="joined", payload={"member": serialize_member(member).dict()})
@Consumer.event('ping_room')
async def proom(self):
await self.broadcast(type='ping', payload={})
def isMember(self):
return self.member is not None and self.member.waiting == False
# Sending Events
@Consumer.sending("joined", conditions=[isMember])
def joined(self, member: MemberRead):
print('MEMBER', self.member, member)
if (self.member.user is not None and member.username == self.member.user.username) or (self.member.anonymous and member.reconnect_code == self.member.anonymous.reconnect_code):
raise ValueError("Nope")
if self.member.is_admin == False:
member.reconnect_code = ""
return {"member": member}
@Consumer.sending('waiter', conditions=[isAdmin])
def waiter(self, waiter: Waiter):
return {"waiter": waiter}
@Consumer.sending('ping', conditions=[isMember])
def ping(self):
return {}
async def disconnect(self): async def disconnect(self):
await self.disconnect_self() self.manager.remove(self.room.id, self)
#await self.disconnect_self()
@router.websocket('/ws/room/{room_id}') @router.websocket('/ws/room/{room_id}')
async def room_ws(ws: WebSocket, room: Room | None = Depends(check_room), db: Session = Depends(get_session)): async def room_ws(ws: WebSocket, room: Room | None = Depends(check_room), db: Session = Depends(get_session)):
if room is None: if room is None:
@ -255,3 +587,32 @@ async def room_ws(ws: WebSocket, room: Room | None = Depends(check_room), db: Se
status_code=status.HTTP_404_NOT_FOUND, detail='Room not found') status_code=status.HTTP_404_NOT_FOUND, detail='Room not found')
consumer = RoomConsumer(ws=ws, room=room, manager=manager, db=db) consumer = RoomConsumer(ws=ws, room=room, manager=manager, db=db)
await consumer.run() await consumer.run()
class TestConsumer(Consumer):
async def connect(self):
await self.ws.accept()
def test(self):
return True
@Consumer.event("test", conditions=[True, test])
async def testering(self):
await self.ws.send_json({"type": "success"})
await self.send({"type": "test", "data": {"i": {"username": "lilian", "reconnect_code": "something", "isAdmin": False, "isUser": False}, "test": 12}})
# await self.send({"type": "test", "data": {"i": {"username": "lilian", "reconnect_code": "something", "isAdmin": False, "isUser": False}}})
return
@Consumer.sending('test', conditions=[])
def sendtest(self, i: MemberRead, test: int):
print("i", i)
print(i.reconnect_code)
print(dict(i))
i.reconnect_code = "nope"
return {"i": i, "test": test}
@router.websocket('/ws/test')
async def test(ws: WebSocket):
consumer = TestConsumer(ws)
await consumer.run()

View File

@ -0,0 +1,14 @@
from pydantic import *
class Model(BaseModel):
test: str = "test"
i: int
d = {'title': 'Sendtest', 'type': 'object', 'properties': {'i': {
'title': 'I', 'type': 'integer'}}, 'required': ['i'], 'additionalProperties': False}
props = {(k) for k,v in d['properties']}
obj = create_model("model", foo=(str, "str"))
print(Model(i=12, l=12))

View File

@ -3,24 +3,26 @@ from fastapi.testclient import TestClient
from tests.test_auth import test_register from tests.test_auth import test_register
def test_create_room_no_auth(client: TestClient): def test_create_room_no_auth(client: TestClient, public=False):
r = client.post('/room', json={"name": "test_room", r = client.post('/room', json={"name": "test_room",
"public": False}, params={'username': "lilian"}) "public": False}, params={'username': "lilian", "public": public})
print(r.json()) print(r.json())
assert "id_code" in r.json()['room'] assert "id_code" in r.json()['room']
assert "reconnect_code" in r.json()['member']['anonymous'] assert r.json()['member']['reconnect_code'] is not None
assert {"room": {**r.json()['room'], 'id_code': None}, "member": {**r.json()['member'], "anonymous": {**r.json()['member']['anonymous'], "reconnect_code": None}}} == {"room": {"id_code": None, "name": "test_room", assert {"room": {**r.json()['room'], 'id_code': None}, "member": {**r.json()['member'], "reconnect_code": None}} == {"room": {"id_code": None, "name": "test_room",
"public": False}, 'member': {"anonymous": {"username": "lilian", "reconnect_code": None}, "user": None}} "public": False}, 'member': {"username": "lilian", "reconnect_code": None, "isUser": False, "isAdmin": True}}
return r.json() return r.json()
def test_create_room_no_auth_invalid(client: TestClient): def test_create_room_no_auth_invalid(client: TestClient):
r = client.post('/room', json={"name": "test_room"*21, r = client.post('/room', json={"name": "test_room"*21,
"public": False}, params={'username': "lilian"*21}) "public": False}, params={'username': "lilian"*21})
print(r.json()) print(r.json())
assert r.json() == {'detail': {'username_error': 'ensure this value has at most 20 characters', assert r.json() == {'detail': {'username_error': 'ensure this value has at most 20 characters',
'name_error': 'ensure this value has at most 20 characters'}} 'name_error': 'ensure this value has at most 20 characters'}}
def test_create_room_auth(client: TestClient, token = None):
def test_create_room_auth(client: TestClient, token=None):
if token is None: if token is None:
token = test_register(client=client)['access'] token = test_register(client=client)['access']
r = client.post('/room', json={"name": "test_room", r = client.post('/room', json={"name": "test_room",
@ -28,52 +30,408 @@ def test_create_room_auth(client: TestClient, token = None):
print(r.json()) print(r.json())
assert "id_code" in r.json()['room'] assert "id_code" in r.json()['room']
assert {**r.json(), "room": {**r.json()['room'], 'id_code': None}} == {"room": {"id_code": None, "name": "test_room", assert {**r.json(), "room": {**r.json()['room'], 'id_code': None}} == {"room": {"id_code": None, "name": "test_room",
"public": False}, 'member': {"user": {"username": "lilian"}, "anonymous": None}} "public": False}, 'member': {"username": "lilian", "reconnect_code": "", "isUser": True, "isAdmin": True}}
return r.json() return r.json()
def test_room_not_found(client: TestClient): def test_room_not_found(client: TestClient):
try: try:
with client.websocket_connect('/ws/room/eee') as r: with client.websocket_connect('/ws/room/eee') as r:
pass pass
except HTTPException as e : except HTTPException as e:
assert True assert True
except Exception: except Exception:
assert False assert False
def test_login_no_auth(client: TestClient): def test_login_no_auth(client: TestClient):
room = test_create_room_no_auth(client=client) room = test_create_room_no_auth(client=client)
member = room['member']['anonymous'] member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws: with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws:
ws.send_json({"type": "login", "data": {"reconnect_code": member['reconnect_code']}}) ws.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
data = ws.receive_json() data = ws.receive_json()
print(data) print(data)
assert data == {'type': "loggedIn", "data": {"member": {"username": member['username'], "reconnect_code": member['reconnect_code'], "isAdmin": True}}} assert data == {'type': "loggedIn", "data": {"member": {
"username": member['username'], "reconnect_code": member['reconnect_code'], "isAdmin": True, "isUser": False}}}
def test_login_no_auth_not_in_room(client: TestClient):
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws:
ws.send_json({"type": "login", "data": {
"reconnect_code": "lol"}})
data = ws.receive_json()
print(data)
assert data == {'type': "error", "data": {
"msg": "Utilisateur introuvable dans cette salle"}}
def test_login_auth(client: TestClient): def test_login_auth(client: TestClient):
token = test_register(client=client)['access'] token = test_register(client=client)['access']
room = test_create_room_auth(client=client, token=token) room = test_create_room_auth(client=client, token=token)
member = room['member']['user'] member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws: with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws:
ws.send_json({"type": "login", "data": {"token": token}}) ws.send_json({"type": "login", "data": {"token": token}})
data = ws.receive_json() data = ws.receive_json()
print(data) print(data)
assert data == {'type': "loggedIn", "data": {"member": {"username": member['username'], "isAdmin": True}}} assert data == {'type': "loggedIn", "data": {"member": {
"username": member['username'], "isAdmin": True, "isUser": True, 'reconnect_code': ""}}}
def test_join_no_auth(client: TestClient):
def test_login_auth_not_in_room(client: TestClient):
token = test_register(client=client, username="lilian2")['access']
room = test_create_room_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as ws:
ws.send_json({"type": "login", "data": {"token": token}})
data = ws.receive_json()
print(data)
assert data == {'type': "error", "data": {
"msg": "Utilisateur introuvable dans cette salle"}}
def test_join_auth(client: TestClient):
token = test_register(client, username="lilian2")['access']
room = test_create_room_no_auth(client=client) room = test_create_room_no_auth(client=client)
member = room['member']['anonymous'] member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin: with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": { admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}}) "reconnect_code": member['reconnect_code']}})
admin.receive_json()
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member: with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type":"join", "data": {"username": "member"}}) member.send_json({"type": "join", "data": {"token": token}})
mdata = member.receive_json()
assert "waiter_id" in mdata['data']['waiter']
assert mdata == {"type": "waiting", "data": {"waiter": {
"username": "lilian2", "waiter_id": mdata['data']['waiter']['waiter_id']}}}
adata = admin.receive_json()
assert adata == {'type': "waiter", 'data': {
"waiter": {"waiter_id": mdata['data']['waiter']['waiter_id'], "username": "lilian2"}}}
admin.send_json({"type": "accept", "data": {
"waiter_id": mdata['data']['waiter']['waiter_id']}})
mdata = member.receive_json()
assert mdata == {"type": "accepted", "data": {"member": {
"username": "lilian2", "isUser": True, "isAdmin": False, "reconnect_code": ""}}}
adata = admin.receive_json()
assert adata == {'type': "joined", 'data': {
"member": {"reconnect_code": "", "username": "lilian2", "isUser": True, "isAdmin": False}}}
admin.send_json({"type": "ping_room"})
mdata = member.receive_json()
assert mdata == {"type": "ping", "data": {}}
def test_join_no_auth(client: TestClient):
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
admin.receive_json()
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json(
{"type": "join", "data": {"username": "member"}})
mdata = memberws.receive_json()
assert "waiter_id" in mdata['data']['waiter']
assert mdata == {"type": "waiting", "data": {"waiter": {
"username": "member", "waiter_id": mdata['data']['waiter']['waiter_id']}}}
adata = admin.receive_json()
assert adata == {'type': "waiter", 'data': {
"waiter": {"waiter_id": mdata['data']['waiter']['waiter_id'], "username": "member"}}}
admin.send_json({"type": "accept", "data": {
"waiter_id": mdata['data']['waiter']['waiter_id']}})
mdata = memberws.receive_json()
new_reconnect = mdata['data']['member']['reconnect_code']
assert 'reconnect_code' in mdata['data']['member']
assert mdata == {"type": "accepted", "data": {"member": {
"username": "member", "reconnect_code": new_reconnect, "isUser": False, "isAdmin": False}}}
adata = admin.receive_json()
assert adata == {'type': "joined", 'data': {
"member": {"reconnect_code": new_reconnect, "username": "member", "isUser": False, "isAdmin": False}}}
admin.send_json({"type": "ping_room"})
mdata = memberws.receive_json()
assert mdata == {"type": "ping", "data": {}}
return {"room": room, "members": [member['reconnect_code'], new_reconnect]}
def test_join_no_auth_username_error(client: TestClient):
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"username": "lilian"}})
mdata = member.receive_json()
assert mdata == {"type": "error", "data": {
"msg": "Nom d'utilisateur invalide ou indisponible"}}
def test_join_no_auth_username_too_long(client: TestClient):
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"username": "lilian"*21}})
mdata = member.receive_json()
assert mdata == {"type": "error", "data": {"msg": "Nom d'utilisateur invalide ou indisponible"}}
def test_join_auth_refused(client: TestClient):
token = test_register(client, username="lilian2")['access']
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
admin.receive_json()
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"token": token}})
mdata = member.receive_json()
assert "waiter_id" in mdata['data']['waiter']
assert mdata == {"type": "waiting", "data": {"waiter": {
"username": "lilian2", "waiter_id": mdata['data']['waiter']['waiter_id']}}}
waiter_id = mdata['data']['waiter']['waiter_id']
adata = admin.receive_json()
assert adata == {'type': "waiter", 'data': {
"waiter": {"waiter_id": waiter_id, "username": "lilian2"}}}
admin.send_json({"type": "refuse", "data": {
"waiter_id": waiter_id}})
mdata = member.receive_json()
assert mdata == {"type": "refused", "data": {
"waiter_id": waiter_id}}
def test_join_auth_in_room_yet(client: TestClient):
token = test_register(client, username="lilian")['access']
room = test_create_room_auth(client=client, token=token)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"token": token}})
mdata = member.receive_json()
assert mdata == {"type": "loggedIn", "data": {"member": {
"username": "lilian2", "isAdmin": True, "isUser": True}}}
def test_join_auth_public(client: TestClient):
token = test_register(client, username="lilian2")['access']
room = test_create_room_no_auth(client=client, public=True)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"token": token}})
mdata = member.receive_json()
assert mdata == {"type": "accepted", "data": {"member": {
"username": "member", "isUser": True, "isAdmin": False}}}
adata = admin.receive_json()
assert adata == {'type': "joined", 'data': {
"member": {"reconnect_code": "", "username": "member", "isUser": True}}}
admin.send_json({"type": "ping_room"})
mdata = member.receive_json()
assert mdata == {"type": "ping"}
def test_join_no_auth_public(client: TestClient):
room = test_create_room_no_auth(client=client, public=True)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"username": "member"}})
mdata = member.receive_json()
assert 'reconnect_code' in mdata['data']['member']
assert mdata == {"type": "accepted", "data": {"member": {
"username": "member", "reconnect_code": mdata['data']['reconnect_code'], "isUser": False, "isAdmin": False}}}
adata = admin.receive_json()
assert adata == {'type': "joined", 'data': {
"member": {"reconnect_code": mdata['data']['reconnect_code'], "username": "member", "isUser": False}}}
member.send_json({"type": "update_groups"})
admin.send_json({"type": "ping_room"})
mdata = member.receive_json()
assert mdata == {"type": "ping"}
def test_join_no_auth_unauthorized(client: TestClient):
token = test_register(client, username="lilian2")['access']
room = test_create_room_no_auth(client=client)
member = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": member['reconnect_code']}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({"type": "join", "data": {"token": token}})
mdata = member.receive_json() mdata = member.receive_json()
assert "id_code" in mdata['data']['waiter'] assert "id_code" in mdata['data']['waiter']
assert mdata == {"type": "waiting", "data": {"waiter": { assert mdata == {"type": "waiting", "data": {"waiter": {
"username": "member", "id_code": mdata['data']['waiter']}}} "username": "lilian2", "id_code": mdata['data']['waiter']}}}
adata = admin.receive_json() adata = admin.receive_json()
assert adata == {'type': "waiter", 'data': { assert adata == {'type': "waiter", 'data': {
"waiter": {"id_code": mdata['data']['waiter'], "username": "member"}}} "waiter": {"id_code": mdata['data']['waiter']['id_code'], "username": "member"}}}
admin.send({"type": "accept", "data": {"waiter_id": mdata['data']['waiter']}}) member.send_json({"type": "refuse", "data": {
"waiter_id": mdata['data']['waiter']['id_code']}})
mdata = member.receive_json()
assert mdata == {"type": "error", "data": {
"msg": "Vous n'avez pas la permission de faire ca"}}
def test_connect_admin(client: TestClient):
room = test_join_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members[0]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as member:
member.send_json({'type': "login", "data": {
"reconnect_code": members[0]}})
adata = admin.receive_json()
assert adata == {"type": "connect", "data": {"member": {
"username": "member", "reconnect_code": members[1], "isAdmin": False, "isUser": False}}}
def test_connect_member(client: TestClient):
room = test_join_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({'type': "login", "data": {
"reconnect_code": members[0]}})
mdata = memberws.receive_json()
assert mdata == {"type": "connect", "data": {"member": {
"username": "member", "reconnect_code": "", "isAdmin": True, "isUser": False}}}
def test_disconnect(client: TestClient):
room = test_join_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({'type': "login", "data": {
"reconnect_code": members[0]}})
admin.close()
mdata = memberws.receive_json()
assert mdata == {"type": "disconnect", "data": {"member": {
"username": "member", "reconnect_code": "", "isAdmin": True, "isUser": False}}}
def test_leave(client: TestClient):
room = test_join_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
memberws.send_json({"type": "leave"})
data = memberws.receive_json()
assert data == {"type": "successfully_leaved"}
adata = admin.receive_json()
assert adata == {"type": "leaved", "data": {"member": {
"username": "member", "reconnect_code": members[1], "isAdmin": False, "isUser": False}}}
def test_leave_not_connected(client: TestClient):
room = test_create_room_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "leave"})
data = memberws.receive_json()
assert data == {"type": "error", "data": {
"msg": "Vous n'êtes connecté à aucune salle"}}
def test_leave_admin(client: TestClient):
room = test_join_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members[0]}})
admin.send_json({"type": "leave"})
data = admin.receive_json()
assert data == {"type": "error", "data": {
"msg": "Vous ne pouvez pas quitter une salle dont vous êtes l'administrateur"}}
def test_ban_anonymous(client: TestClient):
room = test_create_room_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
admin.send_json({"type": "ban", "data": {"member": {
"username": "member", "reconnect_code": members[1], "isUser": False, "isAdmin": False}}})
adata = admin.receive_json()
assert adata == {"type": "leaved", "data": {"member": {
"username": "member", "reconnect_code": members[1], "isUser": True, "isAdmin": False}}}
def test_ban_anonymous_unauthorized(client: TestClient):
room = test_create_room_no_auth(client=client)
members = room['members']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "login", "data": {
"reconnect_code": members[1]}})
memberws.send_json({"type": "ban", "data": {"member": {
"username": "member", "reconnect_code": members[1], "isUser": False, "isAdmin": False}}})
mdata = memberws.receive_json()
assert mdata == {"type": "error", "data": {
"msg": "Vous n'avez pas les permissions pour faire ça"}}
def test_ban_user(client: TestClient):
token = test_register(client=client, username="lilian2")
room = test_create_room_no_auth(client=client)
members = room['member']
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as admin:
admin.send_json({"type": "login", "data": {
"reconnect_code": members["reconnect_code "]}})
with client.websocket_connect(f"/ws/room/" + room['room']['id_code']) as memberws:
memberws.send_json({"type": "join", "data": {"token": token}})
adata = admin.receive_json()
admin.send_json({"type": "ban", "data": {"member": {
"username": "lilian2", "reconnect_code": "", "isUser": True, "isAdmin": False}}})
adata = admin.receive_json()
assert adata == {"type": "leaved", "data": {"member": {
"username": "lilian2", "reconnect_code": "", "isUser": True, "isAdmin": False}}}