From fa413af0538d796d2e2f92063d19ef1e7753ab99 Mon Sep 17 00:00:00 2001 From: Krukov D Date: Wed, 19 Apr 2017 06:18:28 +0300 Subject: [PATCH] Improvements for test client (#613) * Tests for httpclient sending content * Testing ordered consumers * Added docs * Added testing for ordering * Added GET params at HttpClient * Remove blank line * Fix py3 bites * Test Client now support ChannelSocketException * Fix flake and isort * Conflict resolution --- channels/test/base.py | 3 ++ channels/test/websocket.py | 28 ++++++++-- docs/testing.rst | 20 ++++++++ tests/test_wsclient.py | 101 ++++++++++++++++++++++++++++++++++++- 4 files changed, 148 insertions(+), 4 deletions(-) diff --git a/channels/test/base.py b/channels/test/base.py index 41a2eff3d..e3ac34d7f 100644 --- a/channels/test/base.py +++ b/channels/test/base.py @@ -12,6 +12,7 @@ from .. import DEFAULT_CHANNEL_LAYER from ..asgi import ChannelLayerWrapper, channel_layers from ..channel import Group +from ..exceptions import ChannelSocketException from ..message import Message from ..routing import Router, include from ..signals import consumer_finished, consumer_started @@ -134,6 +135,8 @@ def consume(self, channel, fail_on_none=True): try: consumer_started.send(sender=self.__class__) return consumer(message, **kwargs) + except ChannelSocketException as e: + e.run(message) finally: # Copy Django's workaround so we don't actually close DB conns consumer_finished.disconnect(close_old_connections) diff --git a/channels/test/websocket.py b/channels/test/websocket.py index d10f6ae63..66fe21651 100644 --- a/channels/test/websocket.py +++ b/channels/test/websocket.py @@ -20,11 +20,13 @@ class WSClient(Client): """ def __init__(self, **kwargs): + self._ordered = kwargs.pop('ordered', False) super(WSClient, self).__init__(**kwargs) self._session = None self._headers = {} self._cookies = {} self._session_cookie = True + self.order = 0 def set_cookie(self, key, value): """ @@ -76,18 +78,38 @@ def send(self, to, content={}, text=None, path='/'): Send a message to a channel. Adds reply_channel name and channel_session to the message. """ + if to != 'websocket.connect' and '?' in path: + path = path.split('?')[0] + self.channel_layer.send(to, self._get_content(content, text, path)) + self._session_cookie = False + + def _get_content(self, content={}, text=None, path='/'): content = copy.deepcopy(content) content.setdefault('reply_channel', self.reply_channel) - content.setdefault('path', path) + + if '?' in path: + path, query_string = path.split('?') + content.setdefault('path', path) + content.setdefault('query_string', query_string) + else: + content.setdefault('path', path) + content.setdefault('headers', self.headers) + + if self._ordered: + if 'order' in content: + raise ValueError('Do not use "order" manually with "ordered=True"') + content['order'] = self.order + self.order += 1 + text = text or content.get('text', None) + if text is not None: if not isinstance(text, six.string_types): content['text'] = json.dumps(text) else: content['text'] = text - self.channel_layer.send(to, content) - self._session_cookie = False + return content def send_and_consume(self, channel, content={}, text=None, path='/', fail_on_none=True, check_accept=True): """ diff --git a/docs/testing.rst b/docs/testing.rst index cbad8ea0e..b3f6369ba 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -187,6 +187,26 @@ may call ``WSClient.force_login`` (like at django client) with the user object. ``receive`` method by default trying to deserialize json text content of a message, so if you need to pass decoding use ``receive(json=False)``, like in the example. +For testing consumers with ``enforce_ordering`` initialize ``HttpClient`` with ``ordered`` +flag, but if you wanna use your own order don't use it, use content:: + + client = HttpClient(ordered=True) + client.send_and_consume('websocket.receive', text='1', path='/ws') # order = 0 + client.send_and_consume('websocket.receive', text='2', path='/ws') # order = 1 + client.send_and_consume('websocket.receive', text='3', path='/ws') # order = 2 + + # manually + client = HttpClient() + client.send('websocket.receive', content={'order': 0}, text='1') + client.send('websocket.receive', content={'order': 2}, text='2') + client.send('websocket.receive', content={'order': 1}, text='3') + + # calling consume 4 time for `waiting` message with order 1 + client.consume('websocket.receive') + client.consume('websocket.receive') + client.consume('websocket.receive') + client.consume('websocket.receive') + Applying routes --------------- diff --git a/tests/test_wsclient.py b/tests/test_wsclient.py index 9f653264b..a8924caa1 100644 --- a/tests/test_wsclient.py +++ b/tests/test_wsclient.py @@ -2,7 +2,11 @@ from django.http.cookie import parse_cookie -from channels.test import ChannelTestCase, WSClient +from channels import route +from channels.exceptions import ChannelSocketException +from channels.handler import AsgiRequest +from channels.test import ChannelTestCase, WSClient, apply_routes +from channels.sessions import enforce_ordering class WSClientTests(ChannelTestCase): @@ -22,3 +26,98 @@ def test_cookies(self): 'qux': 'qu;x', 'sessionid': client.get_cookies()['sessionid']}, cookie_dict) + + def test_simple_content(self): + client = WSClient() + content = client._get_content(text={'key': 'value'}, path='/my/path') + + self.assertEqual(content['text'], '{"key": "value"}') + self.assertEqual(content['path'], '/my/path') + self.assertTrue('reply_channel' in content) + self.assertTrue('headers' in content) + + def test_path_in_content(self): + client = WSClient() + content = client._get_content(content={'path': '/my_path'}, text={'path': 'hi'}, path='/my/path') + + self.assertEqual(content['text'], '{"path": "hi"}') + self.assertEqual(content['path'], '/my_path') + self.assertTrue('reply_channel' in content) + self.assertTrue('headers' in content) + + def test_session_in_headers(self): + client = WSClient() + content = client._get_content() + self.assertTrue('path' in content) + self.assertEqual(content['path'], '/') + + self.assertTrue('headers' in content) + self.assertTrue('cookie' in content['headers']) + self.assertTrue(b'sessionid' in content['headers']['cookie']) + + def test_ordering_in_content(self): + client = WSClient(ordered=True) + content = client._get_content() + self.assertTrue('order' in content) + self.assertEqual(content['order'], 0) + client.order = 2 + content = client._get_content() + self.assertTrue('order' in content) + self.assertEqual(content['order'], 2) + + def test_ordering(self): + + client = WSClient(ordered=True) + + @enforce_ordering + def consumer(message): + message.reply_channel.send({'text': message['text']}) + + with apply_routes(route('websocket.receive', consumer)): + client.send_and_consume('websocket.receive', text='1') # order = 0 + client.send_and_consume('websocket.receive', text='2') # order = 1 + client.send_and_consume('websocket.receive', text='3') # order = 2 + + self.assertEqual(client.receive(), 1) + self.assertEqual(client.receive(), 2) + self.assertEqual(client.receive(), 3) + + def test_get_params(self): + client = WSClient() + content = client._get_content(path='/my/path?test=1&token=2') + self.assertTrue('path' in content) + self.assertTrue('query_string' in content) + self.assertEqual(content['path'], '/my/path') + self.assertEqual(content['query_string'], 'test=1&token=2') + + def test_get_params_with_consumer(self): + client = WSClient(ordered=True) + + def consumer(message): + message.content['method'] = 'FAKE' + message.reply_channel.send({'text': dict(AsgiRequest(message).GET)}) + + with apply_routes([route('websocket.receive', consumer, path=r'^/test'), + route('websocket.connect', consumer, path=r'^/test')]): + path = '/test?key1=val1&key2=val2&key1=val3' + client.send_and_consume('websocket.connect', path=path, check_accept=False) + self.assertDictEqual(client.receive(), {'key2': ['val2'], 'key1': ['val1', 'val3']}) + + client.send_and_consume('websocket.receive', path=path) + self.assertDictEqual(client.receive(), {}) + + def test_channel_socket_exception(self): + + class MyChannelSocketException(ChannelSocketException): + + def run(self, message): + message.reply_channel.send({'text': 'error'}) + + def consumer(message): + raise MyChannelSocketException + + client = WSClient() + with apply_routes(route('websocket.receive', consumer)): + client.send_and_consume('websocket.receive') + + self.assertEqual(client.receive(json=False), 'error')