Add desktop ready checks, smarter chat scrolling, and fight-night reactions
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""Exercise ready checks through real WebSockets against an isolated local server."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from websockets.sync.client import connect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def server_url():
|
||||
with socket.socket() as reservation:
|
||||
reservation.bind(('127.0.0.1', 0))
|
||||
port = reservation.getsockname()[1]
|
||||
bun = shutil.which('bun')
|
||||
assert bun, 'Bun is required to test the local server'
|
||||
process = subprocess.Popen(
|
||||
[bun, 'run', 'server.ts'], cwd=Path(__file__).resolve().parent.parent,
|
||||
env={**os.environ, 'PORT': str(port)}, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
|
||||
try:
|
||||
deadline = time.monotonic() + 5
|
||||
while True:
|
||||
if process.poll() is not None:
|
||||
pytest.fail(process.stderr.read().decode())
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', port), timeout=.1):
|
||||
break
|
||||
except OSError:
|
||||
if time.monotonic() >= deadline:
|
||||
pytest.fail('Local server did not start')
|
||||
time.sleep(.05)
|
||||
yield f'ws://127.0.0.1:{port}/ws'
|
||||
finally:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
process.stderr.close()
|
||||
|
||||
|
||||
def send(ws, **message):
|
||||
ws.send(json.dumps(message))
|
||||
|
||||
|
||||
def receive(ws, kind, predicate=lambda message: True):
|
||||
deadline = time.monotonic() + 3
|
||||
while True:
|
||||
message = json.loads(ws.recv(timeout=max(.01, deadline - time.monotonic())))
|
||||
if message['type'] == kind and predicate(message):
|
||||
return message
|
||||
|
||||
|
||||
def test_room_ready_check_lifecycle(server_url):
|
||||
file_info = {'name': 'test.mkv', 'size': 1234, 'duration': 60}
|
||||
with connect(server_url) as host, connect(server_url) as guest:
|
||||
send(host, type='create_room', username='Host', fileInfo=file_info)
|
||||
code = receive(host, 'room_created')['code']
|
||||
send(guest, type='join_room', username='Guest', code=code)
|
||||
receive(guest, 'room_file_check')
|
||||
send(guest, type='confirm_join', fileInfo=file_info)
|
||||
receive(guest, 'room_joined')
|
||||
receive(host, 'user_joined')
|
||||
send(host, type='sync', action='play', position=12)
|
||||
receive(guest, 'sync')
|
||||
send(host, type='ready_check', action='begin')
|
||||
pause = receive(host, 'sync')
|
||||
assert pause['action'] == 'pause' and pause['position'] >= 12
|
||||
check = receive(host, 'ready_check')
|
||||
check_id = check['id']
|
||||
assert check['requestedBy'] == 'Host'
|
||||
assert not any(p['ready'] for p in check['participants'])
|
||||
assert receive(guest, 'ready_check')['id'] == check_id
|
||||
|
||||
# Unconfirmed peers cannot create or vote in a check.
|
||||
with connect(server_url) as pending:
|
||||
send(pending, type='join_room', username='Pending', code=code)
|
||||
receive(pending, 'room_file_check')
|
||||
send(pending, type='ready_check', action='begin')
|
||||
send(pending, type='ready_check', action='vote', id=check_id, ready=True)
|
||||
send(host, type='ready_check', action='start', id=check_id)
|
||||
send(host, type='ready_check', action='vote', id=check_id, ready=True)
|
||||
check = receive(host, 'ready_check')
|
||||
assert check['active'] and sum(p['ready'] for p in check['participants']) == 1
|
||||
receive(guest, 'ready_check')
|
||||
send(host, type='ready_check', action='begin')
|
||||
repeated = receive(host, 'ready_check')
|
||||
assert repeated['id'] == check_id
|
||||
assert sum(p['ready'] for p in repeated['participants']) == 1
|
||||
receive(guest, 'ready_check')
|
||||
send(guest, type='ready_check', action='vote', id=check_id, ready=True)
|
||||
assert all(p['ready'] for p in receive(host, 'ready_check')['participants'])
|
||||
receive(guest, 'ready_check')
|
||||
|
||||
# Non-initiators cannot start. Stale IDs cannot change votes.
|
||||
send(guest, type='ready_check', action='start', id=check_id)
|
||||
send(guest, type='ready_check', action='vote', id='stale', ready=False)
|
||||
send(guest, type='ready_check', action='begin')
|
||||
assert all(p['ready'] for p in receive(host, 'ready_check')['participants'])
|
||||
receive(guest, 'ready_check')
|
||||
|
||||
# Dropping a connection revokes readiness and blocks starting.
|
||||
guest.close()
|
||||
disconnected = receive(host, 'ready_check')
|
||||
guest_state = next(p for p in disconnected['participants'] if p['username'] == 'Guest')
|
||||
assert not guest_state['connected'] and not guest_state['ready']
|
||||
send(host, type='ready_check', action='start', id=check_id)
|
||||
with connect(server_url) as rejoined:
|
||||
send(rejoined, type='rejoin_room', username='Guest', code=code)
|
||||
restored = receive(rejoined, 'room_rejoined')['readyCheck']
|
||||
assert restored['id'] == check_id
|
||||
assert not next(p for p in restored['participants'] if p['username'] == 'Guest')['ready']
|
||||
receive(host, 'ready_check')
|
||||
receive(rejoined, 'ready_check')
|
||||
send(rejoined, type='ready_check', action='vote', id=check_id, ready=True)
|
||||
receive(host, 'ready_check')
|
||||
receive(rejoined, 'ready_check')
|
||||
send(host, type='ready_check', action='start', id=check_id)
|
||||
assert receive(host, 'sync')['action'] == 'play'
|
||||
assert receive(rejoined, 'sync')['action'] == 'play'
|
||||
assert not receive(host, 'ready_check')['active']
|
||||
assert not receive(rejoined, 'ready_check')['active']
|
||||
|
||||
send(host, type='ready_check', action='begin')
|
||||
new_check = receive(host, 'ready_check')
|
||||
assert new_check['id'] != check_id
|
||||
assert not any(p['ready'] for p in new_check['participants'])
|
||||
receive(rejoined, 'ready_check')
|
||||
send(host, type='ready_check', action='cancel', id=new_check['id'])
|
||||
assert not receive(host, 'ready_check')['active']
|
||||
assert not receive(rejoined, 'ready_check')['active']
|
||||
Reference in New Issue
Block a user