Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Sep 10, 2023
1 parent 5122519 commit 9c4b5db
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 17 deletions.
24 changes: 19 additions & 5 deletions src/socketio/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,22 @@ def instrument(self):
Socket._websocket_handler = functools.partialmethod(
self.__class__._eio_websocket_handler, self)

def uninstrument(self): # pragma: no cover
if self.mode == 'development':
self.sio.manager.connect = self.sio.manager.__connect
self.sio.manager.disconnect = self.sio.manager.__disconnect
self.sio.manager.enter_room = self.sio.manager.__enter_room
self.sio.manager.leave_room = self.sio.manager.__leave_room
self.sio.manager.emit = self.sio.manager.__emit
self.sio._handle_event_internal = self.sio.__handle_event_internal
self.sio.eio._ok = self.sio.eio.__ok

from engineio.socket import Socket
Socket.handle_post_request = Socket.__handle_post_request
Socket._websocket_handler = Socket.__websocket_handler

def admin_connect(self, sid, environ, client_auth):
if self.auth != None:
if self.auth:
authenticated = False
if isinstance(self.auth, dict):
authenticated = client_auth == self.auth
Expand Down Expand Up @@ -176,7 +190,7 @@ def admin_disconnect(self, _, namespace, close, room_filter=None):

def shutdown(self):
self.stop_stats_event.set()
self.stats_thread.join()
self.stats_task.join()

def _connect(self, eio_sid, namespace):
sid = self.sio.manager.__connect(eio_sid, namespace)
Expand All @@ -188,7 +202,7 @@ def _connect(self, eio_sid, namespace):
datetime.utcfromtimestamp(t).isoformat() + 'Z',
), namespace=self.admin_namespace)

def check_for_upgrade():
def check_for_upgrade(): # pragma: no cover
for _ in range(5):
self.sio.sleep(5)
try:
Expand All @@ -202,7 +216,7 @@ def check_for_upgrade():
except KeyError:
pass

if serialized_socket['transport'] == 'polling':
if serialized_socket['transport'] == 'polling': # pragma: no cover
self.sio.start_background_task(check_for_upgrade)
return sid

Expand Down Expand Up @@ -333,7 +347,7 @@ def _emit_server_stats(self):
}, namespace=self.admin_namespace)

def serialize_socket(self, sid, namespace, eio_sid=None):
if eio_sid is None:
if eio_sid is None: # pragma: no cover
eio_sid = self.sio.manager.eio_sid_from_sid(sid)
socket = self.sio.eio._get_socket(eio_sid)
environ = self.sio.environ.get(eio_sid, {})
Expand Down
8 changes: 4 additions & 4 deletions src/socketio/asyncio_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def instrument(self):

async def admin_connect(self, sid, environ, client_auth):
authenticated = True
if self.auth != None:
if self.auth:
authenticated = False
if isinstance(self.auth, dict):
authenticated = client_auth == self.auth
Expand Down Expand Up @@ -223,9 +223,9 @@ def _leave_room(self, sid, namespace, room):

async def _emit(self, event, data, namespace, room=None, skip_sid=None,
callback=None, **kwargs):
ret = await self.sio.manager.__emit(event, data, namespace, room=room,
skip_sid=skip_sid, callback=callback,
**kwargs)
ret = await self.sio.manager.__emit(
event, data, namespace, room=room, skip_sid=skip_sid,
callback=callback, **kwargs)
if namespace != self.admin_namespace:
event_data = [event] + list(data) if isinstance(data, tuple) \
else [data]
Expand Down
2 changes: 1 addition & 1 deletion src/socketio/asyncio_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def sid(self):
The session ID is not guaranteed to remain constant throughout the life
of the connection, as reconnections can cause it to change.
"""
return self.client.sid if self.client else None
return self.client.get_sid(self.namespace) if self.client else None

@property
def transport(self):
Expand Down
2 changes: 1 addition & 1 deletion src/socketio/simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def sid(self):
The session ID is not guaranteed to remain constant throughout the life
of the connection, as reconnections can cause it to change.
"""
return self.client.sid if self.client else None
return self.client.get_sid(self.namespace) if self.client else None

@property
def transport(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/asyncio/test_asyncio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def test_emit_to_invalid_namespace(self):
_run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo'))

def test_emit_with_tuple(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ('foo', 'bar'), namespace='/foo', room=sid
Expand All @@ -364,7 +364,7 @@ def test_emit_with_tuple(self):
assert pkt.encode() == '42/foo,["my event","foo","bar"]'

def test_emit_with_list(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ['foo', 'bar'], namespace='/foo', room=sid
Expand All @@ -377,7 +377,7 @@ def test_emit_with_list(self):
assert pkt.encode() == '42/foo,["my event",["foo","bar"]]'

def test_emit_with_none(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', None, namespace='/foo', room=sid
Expand All @@ -390,7 +390,7 @@ def test_emit_with_none(self):
assert pkt.encode() == '42/foo,["my event"]'

def test_emit_binary(self):
sid = self.bm.connect('123', '/')
sid = _run(self.bm.connect('123', '/'))
_run(
self.bm.emit(
u'my event', b'my binary data', namespace='/', room=sid
Expand Down
3 changes: 2 additions & 1 deletion tests/asyncio/test_asyncio_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def test_connect_twice(self):

def test_properties(self):
client = AsyncSimpleClient()
client.client = mock.MagicMock(sid='sid', transport='websocket')
client.client = mock.MagicMock(transport='websocket')
client.client.get_sid.return_value = 'sid'
client.connected_event.set()
client.connected = True

Expand Down
198 changes: 198 additions & 0 deletions tests/common/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
from functools import wraps
import unittest
import pytest
import socketio
from socketio.exceptions import ConnectionError
from tests.web_server import SocketIOWebServer


def with_instrumented_server(auth=False, **ikwargs):
"""This decorator can be applied to test functions or methods so that they
run with a Socket.IO server that has been instrumented for the official
Admin UI project. The arguments passed to the decorator are passed directly
to the ``instrument()`` method of the server.
"""
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
sio = socketio.Server(async_mode='threading')
instrumented_server = sio.instrument(auth=auth, **ikwargs)

@sio.event
def enter_room(sid, data):
sio.enter_room(sid, data)

@sio.event(namespace='/foo')
def connect(sid, environ, auth):
pass

server = SocketIOWebServer(sio)
server.start()

# import logging
# logging.getLogger('engineio.client').setLevel(logging.DEBUG)
# logging.getLogger('socketio.client').setLevel(logging.DEBUG)
# logging.getLogger('engineio.server').setLevel(logging.DEBUG)
# logging.getLogger('socketio.server').setLevel(logging.DEBUG)

try:
ret = f(*args, **kwargs)
finally:
server.stop()
instrumented_server.shutdown()
instrumented_server.uninstrument()

# import logging
# logging.getLogger('engineio.client').setLevel(logging.NOTSET)
# logging.getLogger('socketio.client').setLevel(logging.NOTSET)
# logging.getLogger('engineio.server').setLevel(logging.NOTSET)
# logging.getLogger('socketio.server').setLevel(logging.NOTSET)

return ret
return wrapped
return decorator


def custom_auth(auth):
return auth == {'foo': 'bar'}


class TestAdmin(unittest.TestCase):
def test_missing_auth(self):
sio = socketio.Server(async_mode='threading')
with pytest.raises(ValueError):
sio.instrument()

@with_instrumented_server(auth=False)
def test_admin_connect_with_no_auth(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin')
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})

@with_instrumented_server(auth={'foo': 'bar'})
def test_admin_connect_with_dict_auth(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'baz'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin')

@with_instrumented_server(auth=[{'foo': 'bar'},
{'u': 'admin', 'p': 'secret'}])
def test_admin_connect_with_list_auth(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin',
auth={'u': 'admin', 'p': 'secret'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'baz'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin')

@with_instrumented_server(auth=custom_auth)
def test_admin_connect_with_function_auth(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'baz'})
with socketio.SimpleClient() as client:
with pytest.raises(ConnectionError):
client.connect('http://localhost:8900', namespace='/admin')

@with_instrumented_server()
def test_admin_connect_only_admin(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin')
sid = client.sid
expected = ['config', 'all_sockets', 'server_stats']
events = {}
while expected:
data = client.receive(timeout=5)
if data[0] in expected:
events[data[0]] = data[1]
expected.remove(data[0])
assert 'supportedFeatures' in events['config']
assert len(events['all_sockets']) == 1
assert events['all_sockets'][0]['id'] == sid
assert events['all_sockets'][0]['rooms'] == [sid]
assert events['server_stats']['clientsCount'] == 1
assert events['server_stats']['pollingClientsCount'] == 0
assert len(events['server_stats']['namespaces']) == 3
assert {'name': '/', 'socketsCount': 0} in \
events['server_stats']['namespaces']
assert {'name': '/foo', 'socketsCount': 0} in \
events['server_stats']['namespaces']
assert {'name': '/admin', 'socketsCount': 1} in \
events['server_stats']['namespaces']

@with_instrumented_server()
def test_admin_connect_with_others(self):
client1 = socketio.SimpleClient()
client1.connect('http://localhost:8900')
client1.emit('enter_room', 'room')
sid1 = client1.sid

client2 = socketio.SimpleClient()
client2.connect('http://localhost:8900', namespace='/foo')
sid2 = client2.sid

client3 = socketio.SimpleClient()
client3.connect('http://localhost:8900', namespace='/admin')
sid3 = client3.sid

with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin')
sid = client.sid
expected = ['config', 'all_sockets', 'server_stats']
events = {}
while expected:
data = client.receive(timeout=5)
if data[0] in expected:
events[data[0]] = data[1]
expected.remove(data[0])

client3.disconnect()
client2.disconnect()
client1.disconnect()

assert 'supportedFeatures' in events['config']
assert len(events['all_sockets']) == 4
assert events['server_stats']['clientsCount'] == 4
assert events['server_stats']['pollingClientsCount'] == 0
assert len(events['server_stats']['namespaces']) == 3
assert {'name': '/', 'socketsCount': 1} in \
events['server_stats']['namespaces']
assert {'name': '/foo', 'socketsCount': 1} in \
events['server_stats']['namespaces']
assert {'name': '/admin', 'socketsCount': 2} in \
events['server_stats']['namespaces']

for socket in events['all_sockets']:
if socket['id'] == sid:
assert socket['rooms'] == [sid]
elif socket['id'] == sid1:
assert socket['rooms'] == [sid1, 'room']
elif socket['id'] == sid2:
assert socket['rooms'] == [sid2]
elif socket['id'] == sid3:
assert socket['rooms'] == [sid3]


# instrument in production mode
# instrument production and read-only
# admin emit, join, leave, disconnect
3 changes: 2 additions & 1 deletion tests/common/test_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def test_connect_twice(self):

def test_properties(self):
client = SimpleClient()
client.client = mock.MagicMock(sid='sid', transport='websocket')
client.client = mock.MagicMock(transport='websocket')
client.client.get_sid.return_value = 'sid'
client.connected_event.set()
client.connected = True

Expand Down
Loading

0 comments on commit 9c4b5db

Please sign in to comment.