From 627dc52d59a9888162e918f4baa2398598e90611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 4 Apr 2018 19:58:44 +0200 Subject: [PATCH 01/85] RSH1a New push.admin.publish --- ably/__init__.py | 1 + ably/rest/push.py | 37 ++++++++++++++++++++++++++++++++++++ ably/rest/rest.py | 6 ++++++ test/ably/restpush_test.py | 39 ++++++++++++++++++++++++++++++++++++++ test/ably/utils.py | 3 ++- 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 ably/rest/push.py create mode 100644 test/ably/restpush_test.py diff --git a/ably/__init__.py b/ably/__init__.py index 06e66787..c66e7957 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -21,6 +21,7 @@ def createLock(self): from ably.rest.rest import AblyRest from ably.rest.auth import Auth +from ably.rest.push import Push from ably.types.capability import Capability from ably.types.options import Options from ably.util.crypto import CipherParams diff --git a/ably/rest/push.py b/ably/rest/push.py new file mode 100644 index 00000000..dc226262 --- /dev/null +++ b/ably/rest/push.py @@ -0,0 +1,37 @@ + +class Push(object): + + def __init__(self, ably): + self.__ably = ably + self.__admin = PushAdmin(ably) + + @property + def admin(self): + return self.__admin + + +class PushAdmin(object): + + def __init__(self, ably): + self.__ably = ably + + @property + def ably(self): + return self.__ably + + def publish(self, recipient, data): + if not isinstance(recipient, dict): + raise TypeError('Unexpected %s recipient, expected a dict' % type(recipient)) + + if not isinstance(data, dict): + raise TypeError('Unexpected %s data, expected a dict' % type(recipient)) + + if not recipient: + raise ValueError('recipient is empty') + + if not data: + raise ValueError('data is empty') + + body = data.copy() + body.update({'recipient': recipient}) + return self.ably.http.post('/push/publish', body=body) diff --git a/ably/rest/rest.py b/ably/rest/rest.py index c6749f3f..21ad0b5a 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -9,6 +9,7 @@ from ably.http.paginatedresult import PaginatedResult, HttpPaginatedResponse from ably.rest.auth import Auth from ably.rest.channel import Channels +from ably.rest.push import Push from ably.util.exceptions import AblyException, catch_all from ably.types.options import Options from ably.types.stats import make_stats_response_processor @@ -75,6 +76,7 @@ def __init__(self, key=None, token=None, token_details=None, **kwargs): self.__channels = Channels(self) self.__options = options + self.__push = Push(self) def set_variant(self, variant): """Sets library variant as per RSC7b""" @@ -146,6 +148,10 @@ def http(self): def options(self): return self.__options + @property + def push(self): + return self.__push + def request(self, method, path, params=None, body=None, headers=None): url = path if params: diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py new file mode 100644 index 00000000..46eafdd4 --- /dev/null +++ b/test/ably/restpush_test.py @@ -0,0 +1,39 @@ +import pytest +import six + +from ably import AblyRest + +from test.ably.restsetup import RestSetup +from test.ably.utils import VaryByProtocolTestsMetaclass, BaseTestCase + +test_vars = RestSetup.get_test_vars() + + +@six.add_metaclass(VaryByProtocolTestsMetaclass) +class TestPush(BaseTestCase): + + def setUp(self): + self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], + rest_host=test_vars["host"], + port=test_vars["port"], + tls_port=test_vars["tls_port"], + tls=test_vars["tls"]) + + def per_protocol_setup(self, use_binary_protocol): + self.ably.options.use_binary_protocol = use_binary_protocol + + # RSH1a + def test_admin_publish(self): + recipient = {'clientId': 'ablyChannel'} + data = { + 'data': {'foo': 'bar'}, + } + + publish = self.ably.push.admin.publish + with pytest.raises(TypeError): publish('ablyChannel', data) + with pytest.raises(TypeError): publish(recipient, 25) + with pytest.raises(ValueError): publish({}, data) + with pytest.raises(ValueError): publish(recipient, {}) + + response = publish(recipient, data) + assert response.status_code == 204 diff --git a/test/ably/utils.py b/test/ably/utils.py index f531c1e0..e559e228 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -62,7 +62,8 @@ def test_decorated(self, *args, **kwargs): for response in responses: if protocol == 'json': self.assertEquals(response.headers['content-type'], 'application/json') - json.loads(response.text) + if response.content: + json.loads(response.text) else: self.assertEquals(response.headers['content-type'], 'application/x-msgpack') if response.content: From 4a81097557c13d5c5173a4ed7a9a20184b5b5ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 5 Apr 2018 12:29:30 +0200 Subject: [PATCH 02/85] RSH1a push.admin.publish, add timeout and docstring --- ably/rest/push.py | 10 ++++++++-- test/ably/utils.py | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index dc226262..4ee73804 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -19,7 +19,13 @@ def __init__(self, ably): def ably(self): return self.__ably - def publish(self, recipient, data): + def publish(self, recipient, data, timeout=None): + """Publish a push notification to a single device. + + :Parameters: + - `recipient`: the recipient of the notification + - `data`: the data of the notification + """ if not isinstance(recipient, dict): raise TypeError('Unexpected %s recipient, expected a dict' % type(recipient)) @@ -34,4 +40,4 @@ def publish(self, recipient, data): body = data.copy() body.update({'recipient': recipient}) - return self.ably.http.post('/push/publish', body=body) + return self.ably.http.post('/push/publish', body=body, timeout=timeout) diff --git a/test/ably/utils.py b/test/ably/utils.py index e559e228..28a51067 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -1,6 +1,5 @@ import unittest -import json from functools import wraps import msgpack @@ -63,7 +62,7 @@ def test_decorated(self, *args, **kwargs): if protocol == 'json': self.assertEquals(response.headers['content-type'], 'application/json') if response.content: - json.loads(response.text) + response.json() else: self.assertEquals(response.headers['content-type'], 'application/x-msgpack') if response.content: From eca7150d9bf1be027750427ac65429efe54604c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 17 Apr 2018 10:58:06 +0200 Subject: [PATCH 03/85] Document how to configure logging Fixes #107 --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4f3305fe..ac555b14 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,20 @@ client = AblyRest('api:key') channel = client.channels.get('channel_name') ``` +You can define the logging level for the whole library, and override for an +specific module: + + import logging + import ably + + logging.getLogger('ably').setLevel(logging.WARNING) + logging.getLogger('ably.rest.auth').setLevel(logging.INFO) + +You need to add a handler to see any output: + + logger = logging.getLogger('ably') + logger.addHandler(logging.StreamHandler()) + ### Publishing a message to a channel ```python From c6d67bf043edf3c8216ad69c5f4ae1407dd70f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 26 Apr 2018 09:38:59 +0200 Subject: [PATCH 04/85] RHS1b3 New push.admin.device_registrations.save --- ably/__init__.py | 1 + ably/http/http.py | 4 ++ ably/rest/push.py | 28 +++++++++++++ ably/types/device.py | 74 +++++++++++++++++++++++++++++++++++ test/ably/restpush_test.py | 42 +++++++++++++++++++- test/ably/restrequest_test.py | 7 +--- test/ably/utils.py | 16 ++++++-- 7 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 ably/types/device.py diff --git a/ably/__init__.py b/ably/__init__.py index c66e7957..fc72ac85 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -23,6 +23,7 @@ def createLock(self): from ably.rest.auth import Auth from ably.rest.push import Push from ably.types.capability import Capability +from ably.types.device import DeviceDetails from ably.types.options import Options from ably.util.crypto import CipherParams from ably.util.exceptions import AblyException, AblyAuthException, IncompatibleClientIdException diff --git a/ably/http/http.py b/ably/http/http.py index 8f077e58..456b20bd 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -195,6 +195,10 @@ def post(self, url, headers=None, body=None, skip_auth=False, timeout=None): return self.make_request('POST', url, headers=headers, body=body, skip_auth=skip_auth, timeout=timeout) + def put(self, url, headers=None, body=None, skip_auth=False, timeout=None): + return self.make_request('PUT', url, headers=headers, body=body, + skip_auth=skip_auth, timeout=timeout) + def delete(self, url, headers=None, skip_auth=False, timeout=None): return self.make_request('DELETE', url, headers=headers, skip_auth=skip_auth, timeout=timeout) diff --git a/ably/rest/push.py b/ably/rest/push.py index 4ee73804..dad765d0 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,3 +1,4 @@ +from ably.types.device import DeviceDetails class Push(object): @@ -14,11 +15,16 @@ class PushAdmin(object): def __init__(self, ably): self.__ably = ably + self.__device_registrations = PushDeviceRegistrations(ably) @property def ably(self): return self.__ably + @property + def device_registrations(self): + return self.__device_registrations + def publish(self, recipient, data, timeout=None): """Publish a push notification to a single device. @@ -41,3 +47,25 @@ def publish(self, recipient, data, timeout=None): body = data.copy() body.update({'recipient': recipient}) return self.ably.http.post('/push/publish', body=body, timeout=timeout) + + +class PushDeviceRegistrations(object): + + def __init__(self, ably): + self.__ably = ably + + @property + def ably(self): + return self.__ably + + def save(self, device): + """Creates or updates the device. Returns a DeviceDetails object. + + :Parameters: + - `device`: a dictionary with the device information + """ + device_details = DeviceDetails(**device) + path = '/push/deviceRegistrations/%s' % device_details.id + response = self.ably.http.put(path, body=device) + details = response.to_native() + return DeviceDetails(**details) diff --git a/ably/types/device.py b/ably/types/device.py new file mode 100644 index 00000000..3d2a21b8 --- /dev/null +++ b/ably/types/device.py @@ -0,0 +1,74 @@ +DevicePushTransportType = {'fcm', 'gcm', 'apns', 'web'} +DevicePlatform = {'android', 'ios', 'browser'} +DeviceFormFactor = {'phone', 'tablet', 'desktop', 'tv', 'watch', 'car', 'embedded', 'other'} + + +class DeviceDetails(object): + + def __init__(self, id, clientId=None, formFactor=None, metadata=None, + platform=None, push=None, updateToken=None, + deviceSecret=None, appId=None, deviceIdentityToken=None): + + if push: + recipient = push.get('recipient') + if recipient: + transport_type = recipient.get('transportType') + if transport_type is not None and transport_type not in DevicePushTransportType: + raise ValueError('unexpected transport type {}'.format(transport_type)) + + if platform is not None and platform not in DevicePlatform: + raise ValueError('unexpected platform {}'.format(platform)) + + if formFactor is not None and formFactor not in DeviceFormFactor: + raise ValueError('unexpected form factor {}'.format(formFactor)) + + self.__id = id + self.__client_id = clientId + self.__form_factor = formFactor + self.__metadata = metadata + self.__platform = platform + self.__push = push + self.__update_token = updateToken + self.__device_secret = deviceSecret + self.__app_id = appId + self.__device_identity_token = deviceIdentityToken + + @property + def id(self): + return self.__id + + @property + def client_id(self): + return self.__client_id + + @property + def form_factor(self): + return self.__form_factor + + @property + def metadata(self): + return self.__metadata + + @property + def platform(self): + return self.__platform + + @property + def push(self): + return self.__push + + @property + def update_token(self): + return self.__update_token + + @property + def device_secret(self): + return self.__device_secret + + @property + def app_id(self): + return self.__app_id + + @property + def device_identity_token(self): + return self.__device_identity_token diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 46eafdd4..6c0b1f88 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -1,10 +1,13 @@ +import string + import pytest import six -from ably import AblyRest +from ably import AblyRest, AblyException, DeviceDetails from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, BaseTestCase +from test.ably.utils import new_dict, random_string test_vars = RestSetup.get_test_vars() @@ -37,3 +40,40 @@ def test_admin_publish(self): response = publish(recipient, data) assert response.status_code == 204 + + # RSH1b3 + def test_admin_device_registrations_save(self): + save = self.ably.push.admin.device_registrations.save + + device_id = random_string(26, string.ascii_uppercase + string.digits) + data = { + 'id': device_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' + } + }, + 'deviceSecret': random_string(12), + } + + # Create + device_details = save(data) + assert type(device_details) is DeviceDetails + + # Update + save(new_dict(data, formFactor='tablet')) + + # Invalid values + with pytest.raises(ValueError): + save(new_dict(data, push={'recipient': new_dict(data['push']['recipient'], transportType='xyz')})) + with pytest.raises(ValueError): + save(new_dict(data, platform='native')) + with pytest.raises(ValueError): + save(new_dict(data, formFactor='fridge')) + + # Fail + with pytest.raises(AblyException): + save(new_dict(data, deviceSecret=random_string(12))) diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index c545cdc1..2ad2be6b 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -1,7 +1,3 @@ -#import time -import random -import string - import requests import six @@ -10,6 +6,7 @@ from test.ably.restsetup import RestSetup from test.ably.utils import BaseTestCase from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol +from test.ably.utils import random_string test_vars = RestSetup.get_test_vars() @@ -27,7 +24,7 @@ def setUpClass(cls): tls=test_vars["tls"]) # Populate the channel (using the new api) - cls.channel = ''.join([random.choice(string.ascii_letters) for x in range(8)]) + cls.channel = random_string(8) cls.path = '/channels/%s/messages' % cls.channel for i in range(20): body = {'name': 'event%s' % i, 'data': 'lorem ipsum %s' % i} diff --git a/test/ably/utils.py b/test/ably/utils.py index 28a51067..1cf4f4ae 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -1,6 +1,7 @@ - +import functools +import random +import string import unittest -from functools import wraps import msgpack import mock @@ -50,7 +51,7 @@ def unpatch(patcher): patcher.stop() def test_decorator(fn): - @wraps(fn) + @functools.wraps(fn) def test_decorated(self, *args, **kwargs): patcher = patch() fn(self, *args, **kwargs) @@ -115,3 +116,12 @@ def wrapper(self): def dont_vary_protocol(func): func.dont_vary_protocol = True return func + + +def random_string(length, alphabet=string.ascii_letters): + return ''.join([random.choice(alphabet) for x in range(length)]) + +def new_dict(src, **kw): + new = src.copy() + new.update(kw) + return new From e554bdcdd62cc46f088fb765c3803dac9631fb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 7 May 2018 09:56:31 +0200 Subject: [PATCH 05/85] Keep device token in module variable Will be reused in future PRs --- test/ably/restpush_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 6c0b1f88..56916e5a 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -12,6 +12,9 @@ test_vars = RestSetup.get_test_vars() +DEVICE_TOKEN = '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' + + @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestPush(BaseTestCase): @@ -53,7 +56,7 @@ def test_admin_device_registrations_save(self): 'push': { 'recipient': { 'transportType': 'apns', - 'deviceToken': '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' + 'deviceToken': DEVICE_TOKEN, } }, 'deviceSecret': random_string(12), From 1d94beb3577950c87b6571c12ef1cbed8e07d844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 12 Apr 2018 10:43:12 +0200 Subject: [PATCH 06/85] RSH1b1 New push.admin.device_registrations.get --- ably/rest/push.py | 12 ++++++++++++ test/ably/restpush_test.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/ably/rest/push.py b/ably/rest/push.py index dad765d0..e870f324 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -58,6 +58,18 @@ def __init__(self, ably): def ably(self): return self.__ably + def get(self, device_id): + """Returns a DeviceDetails object if the device id is found or results + in a not found error if the device cannot be found. + + :Parameters: + - `device_id`: the id of the device + """ + path = '/push/deviceRegistrations/%s' % device_id + response = self.ably.http.get(path) + details = response.to_native() + return DeviceDetails(**details) + def save(self, device): """Creates or updates the device. Returns a DeviceDetails object. diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 56916e5a..936eb089 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -44,6 +44,37 @@ def test_admin_publish(self): response = publish(recipient, data) assert response.status_code == 204 + # RSH1b1 + def test_admin_device_registrations_get(self): + get = self.ably.push.admin.device_registrations.get + + # Not found + with pytest.raises(AblyException): + get('not-found') + + # Save + device_id = random_string(26, string.ascii_uppercase + string.digits) + data = { + 'id': device_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': DEVICE_TOKEN + } + }, + 'deviceSecret': random_string(12), + } + self.ably.push.admin.device_registrations.save(data) + + # Found + device_details = get(device_id) + assert device_details.id == device_id + assert device_details.platform == data['platform'] + assert device_details.form_factor == data['formFactor'] + assert device_details.device_secret == data['deviceSecret'] + # RSH1b3 def test_admin_device_registrations_save(self): save = self.ably.push.admin.device_registrations.save From 5ff286b564552124f2ffe2d4e58428f2712b1070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 12 Apr 2018 15:53:53 +0200 Subject: [PATCH 07/85] RHS1b2 New push.admin.device_registrations.list --- ably/http/paginatedresult.py | 34 ++++++++++++++++++++++++++ ably/rest/channel.py | 29 ++++------------------- ably/rest/push.py | 16 ++++++++++++- ably/rest/rest.py | 31 +++--------------------- ably/types/device.py | 15 ++++++++++++ ably/types/stats.py | 14 +++++------ test/ably/restpush_test.py | 46 ++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 62 deletions(-) diff --git a/ably/http/paginatedresult.py b/ably/http/paginatedresult.py index 00591f41..a9b14ae7 100644 --- a/ably/http/paginatedresult.py +++ b/ably/http/paginatedresult.py @@ -1,12 +1,46 @@ from __future__ import absolute_import +import calendar import logging +from six.moves.urllib.parse import urlencode + from ably.http.http import Request log = logging.getLogger(__name__) +def format_time_param(t): + try: + return '%d' % (calendar.timegm(t.utctimetuple()) * 1000) + except: + return str(t) + +def format_params(params=None, direction=None, start=None, end=None, limit=None, **kw): + if params is None: + params = {} + + for key, value in kw.items(): + if value is not None: + params[key] = value + + if direction: + params['direction'] = str(direction) + if start: + params['start'] = format_time_param(start) + if end: + params['end'] = format_time_param(end) + if limit: + if limit > 1000: + raise ValueError("The maximum allowed limit is 1000") + params['limit'] = '%d' % limit + + if 'start' in params and 'end' in params and params['start'] > params['end']: + raise ValueError("'end' parameter has to be greater than or equal to 'start'") + + return '?' + urlencode(params) if params else '' + + class PaginatedResult(object): def __init__(self, http, items, content_type, rel_first, rel_next, response_processor, response): diff --git a/ably/rest/channel.py b/ably/rest/channel.py index b894e841..176301c4 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -1,15 +1,14 @@ from __future__ import absolute_import -import calendar import logging import json from collections import OrderedDict import six import msgpack -from six.moves.urllib.parse import urlencode, quote +from six.moves.urllib.parse import quote -from ably.http.paginatedresult import PaginatedResult +from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.message import ( Message, make_message_response_handler, make_encrypted_message_response_handler, MessageJSONEncoder) @@ -29,32 +28,12 @@ def __init__(self, ably, name, options): self.options = options self.__presence = Presence(self) - def _format_time_param(self, t): - try: - return '%d' % (calendar.timegm(t.utctimetuple()) * 1000) - except: - return '%s' % t - @catch_all def history(self, direction=None, limit=None, start=None, end=None, timeout=None): """Returns the history for this channel""" - params = {} - - if direction: - params['direction'] = '%s' % direction - if limit: - if limit > 1000: - raise ValueError("The maximum allowed limit is 1000") - params['limit'] = '%d' % limit - if start: - params['start'] = self._format_time_param(start) - if end: - params['end'] = self._format_time_param(end) - + params = format_params({}, direction=direction, start=start, end=end, limit=limit) path = '/channels/%s/history' % self.__name - - if params: - path = path + '?' + urlencode(params) + path += params if self.__cipher: message_handler = make_encrypted_message_response_handler( diff --git a/ably/rest/push.py b/ably/rest/push.py index e870f324..d701cd84 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,4 +1,5 @@ -from ably.types.device import DeviceDetails +from ably.http.paginatedresult import PaginatedResult, format_params +from ably.types.device import DeviceDetails, make_device_details_response_processor class Push(object): @@ -70,6 +71,19 @@ def get(self, device_id): details = response.to_native() return DeviceDetails(**details) + def list(self, **params): + """Returns a PaginatedResult object with the list of DeviceDetails + objects, filterede by the given parameters. + + :Parameters: + - `**params`: the parameters used to filter the list + """ + path = '/push/deviceRegistrations' + format_params(params) + response_processor = make_device_details_response_processor( + self.ably.options.use_binary_protocol) + return PaginatedResult.paginated_query( + self.ably.http, url=path, response_processor=response_processor) + def save(self, device): """Creates or updates the device. Returns a DeviceDetails object. diff --git a/ably/rest/rest.py b/ably/rest/rest.py index 21ad0b5a..41023fa7 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -1,12 +1,12 @@ from __future__ import absolute_import -import calendar import logging from six.moves.urllib.parse import urlencode from ably.http.http import Http from ably.http.paginatedresult import PaginatedResult, HttpPaginatedResponse +from ably.http.paginatedresult import format_params from ably.rest.auth import Auth from ably.rest.channel import Channels from ably.rest.push import Push @@ -82,37 +82,12 @@ def set_variant(self, variant): """Sets library variant as per RSC7b""" self.variant = variant - def _format_time_param(self, t): - try: - return '%d' % (calendar.timegm(t.utctimetuple()) * 1000) - except: - return '%s' % t - @catch_all def stats(self, direction=None, start=None, end=None, params=None, limit=None, paginated=None, unit=None, timeout=None): """Returns the stats for this application""" - params = params or {} - - if direction: - params["direction"] = direction - if start: - params["start"] = self._format_time_param(start) - if end: - params["end"] = self._format_time_param(end) - if limit: - if limit > 1000: - raise ValueError("The maximum allowed limit is 1000") - params["limit"] = limit - if unit: - params["unit"] = unit - - if 'start' in params and 'end' in params and params['start'] > params['end']: - raise ValueError("'end' parameter has to be greater than or equal to 'start'") - - url = '/stats' - if params: - url += '?' + urlencode(params) + params = format_params(params, direction=direction, start=start, end=end, limit=limit, unit=unit) + url = '/stats' + params stats_response_processor = make_stats_response_processor( self.options.use_binary_protocol) diff --git a/ably/types/device.py b/ably/types/device.py index 3d2a21b8..6cb9cbdf 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -72,3 +72,18 @@ def app_id(self): @property def device_identity_token(self): return self.__device_identity_token + + @classmethod + def from_array(cls, array): + return [cls.from_dict(d) for d in array] + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +def make_device_details_response_processor(binary): + def device_details_response_processor(response): + native = response.to_native() + return DeviceDetails.from_array(native) + return device_details_response_processor diff --git a/ably/types/stats.py b/ably/types/stats.py index 70ca6e16..2c39a3f9 100644 --- a/ably/types/stats.py +++ b/ably/types/stats.py @@ -3,8 +3,6 @@ import logging from datetime import datetime -import msgpack - log = logging.getLogger(__name__) @@ -124,8 +122,8 @@ def __init__(self, all=None, inbound=None, outbound=None, persisted=None, granularity_from_interval_id(self.interval_id)) self.interval_time = interval_from_interval_id(self.interval_id) - @staticmethod - def from_dict(stats_dict): + @classmethod + def from_dict(cls, stats_dict): stats_dict = stats_dict or {} kwargs = { @@ -141,11 +139,11 @@ def from_dict(stats_dict): "interval_id": stats_dict.get("intervalId") } - return Stats(**kwargs) + return cls(**kwargs) - @staticmethod - def from_array(stats_array): - return [Stats.from_dict(d) for d in stats_array] + @classmethod + def from_array(cls, stats_array): + return [cls.from_dict(d) for d in stats_array] @staticmethod def to_interval_id(date_time, granularity): diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 936eb089..adda868a 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -4,6 +4,7 @@ import six from ably import AblyRest, AblyException, DeviceDetails +from ably.http.paginatedresult import PaginatedResult from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, BaseTestCase @@ -75,6 +76,51 @@ def test_admin_device_registrations_get(self): assert device_details.form_factor == data['formFactor'] assert device_details.device_secret == data['deviceSecret'] + # RSH1b2 + def test_admin_device_registrations_list(self): + datas = [] + for i in range(10): + device_id = random_string(26, string.ascii_uppercase + string.digits) + client_id = random_string(12) + data = { + 'id': device_id, + 'clientId': client_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' + } + }, + 'deviceSecret': random_string(12), + } + self.ably.push.admin.device_registrations.save(data) + datas.append(data) + + response = self.ably.push.admin.device_registrations.list() + assert type(response) is PaginatedResult + assert type(response.items) is list + assert type(response.items[0]) is DeviceDetails + + # limit + response = self.ably.push.admin.device_registrations.list(limit=2) + assert len(response.items) == 2 + + # Filter by device id + first = datas[0] + response = self.ably.push.admin.device_registrations.list(deviceId=first['id']) + assert len(response.items) == 1 + response = self.ably.push.admin.device_registrations.list( + deviceId=random_string(26, string.ascii_uppercase + string.digits)) + assert len(response.items) == 0 + + # Filter by client id + response = self.ably.push.admin.device_registrations.list(clientId=first['clientId']) + assert len(response.items) == 1 + response = self.ably.push.admin.device_registrations.list(clientId=random_string(12)) + assert len(response.items) == 0 + # RSH1b3 def test_admin_device_registrations_save(self): save = self.ably.push.admin.device_registrations.save From 476038ad9bc1bfd79623cc088f58c382201ea892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 8 Jun 2018 14:01:21 +0200 Subject: [PATCH 08/85] push: improve list limit tests, fix typo --- ably/rest/push.py | 2 +- test/ably/restpush_test.py | 37 +++++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index d701cd84..60f34f5a 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -73,7 +73,7 @@ def get(self, device_id): def list(self, **params): """Returns a PaginatedResult object with the list of DeviceDetails - objects, filterede by the given parameters. + objects, filtered by the given parameters. :Parameters: - `**params`: the parameters used to filter the list diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index adda868a..71ad20e8 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -19,7 +19,10 @@ @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestPush(BaseTestCase): - def setUp(self): + count = 0 # Number of devices registered + + @classmethod + def setUpClass(self): self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], rest_host=test_vars["host"], port=test_vars["port"], @@ -29,6 +32,16 @@ def setUp(self): def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol + @classmethod + def __save(self, data): + """ + Wrapps calls to save, to keep a count on the numer of devices + registered. + """ + result = self.ably.push.admin.device_registrations.save(data) + self.count += 1 + return result + # RSH1a def test_admin_publish(self): recipient = {'clientId': 'ablyChannel'} @@ -67,7 +80,7 @@ def test_admin_device_registrations_get(self): }, 'deviceSecret': random_string(12), } - self.ably.push.admin.device_registrations.save(data) + self.__save(data) # Found device_details = get(device_id) @@ -90,12 +103,12 @@ def test_admin_device_registrations_list(self): 'push': { 'recipient': { 'transportType': 'apns', - 'deviceToken': '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' + 'deviceToken': DEVICE_TOKEN, } }, 'deviceSecret': random_string(12), } - self.ably.push.admin.device_registrations.save(data) + self.__save(data) datas.append(data) response = self.ably.push.admin.device_registrations.list() @@ -104,6 +117,8 @@ def test_admin_device_registrations_list(self): assert type(response.items[0]) is DeviceDetails # limit + response = self.ably.push.admin.device_registrations.list(limit=5000) + assert len(response.items) == self.count response = self.ably.push.admin.device_registrations.list(limit=2) assert len(response.items) == 2 @@ -123,8 +138,6 @@ def test_admin_device_registrations_list(self): # RSH1b3 def test_admin_device_registrations_save(self): - save = self.ably.push.admin.device_registrations.save - device_id = random_string(26, string.ascii_uppercase + string.digits) data = { 'id': device_id, @@ -140,20 +153,20 @@ def test_admin_device_registrations_save(self): } # Create - device_details = save(data) + device_details = self.__save(data) assert type(device_details) is DeviceDetails # Update - save(new_dict(data, formFactor='tablet')) + self.__save(new_dict(data, formFactor='tablet')) # Invalid values with pytest.raises(ValueError): - save(new_dict(data, push={'recipient': new_dict(data['push']['recipient'], transportType='xyz')})) + self.__save(new_dict(data, push={'recipient': new_dict(data['push']['recipient'], transportType='xyz')})) with pytest.raises(ValueError): - save(new_dict(data, platform='native')) + self.__save(new_dict(data, platform='native')) with pytest.raises(ValueError): - save(new_dict(data, formFactor='fridge')) + self.__save(new_dict(data, formFactor='fridge')) # Fail with pytest.raises(AblyException): - save(new_dict(data, deviceSecret=random_string(12))) + self.__save(new_dict(data, deviceSecret=random_string(12))) From 8b6088d9e77c14af2595d0fc78c81ccf50d78be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 8 Jun 2018 16:21:45 +0200 Subject: [PATCH 09/85] Fixing tests, randomize channel names --- test/ably/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ably/utils.py b/test/ably/utils.py index 1cf4f4ae..db0d5ea6 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -16,8 +16,9 @@ def responses_add_empty_msg_pack(self, url, method=responses.GET): responses.add(responses.GET, url, body=msgpack.packb({}), content_type='application/x-msgpack') - def protocol_channel_name(self, name): - return name + ('_bin' if self.use_binary_protocol else '_text') + def protocol_channel_name(self, prefix=''): + suffix = '_bin' if self.use_binary_protocol else '_text' + return prefix + random_string(8) + suffix def assert_responses_type(protocol): From f45f9dd76b3eec2d2d1ee0dab18c3a0eaccf2aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 14 Jun 2018 16:35:22 +0200 Subject: [PATCH 10/85] Radomizing channel names Rename to get_channel_name, as it makes more sense imho. Make it a classmethod, so it can be called from setUpClass. Use assert in a few places (we should do that everywhere, now we use pytest). --- test/ably/restauth_test.py | 4 +-- test/ably/restchannelhistory_test.py | 43 +++++++++++----------------- test/ably/restchannelpublish_test.py | 30 +++++++++---------- test/ably/restcrypto_test.py | 8 +++--- test/ably/restrequest_test.py | 3 +- test/ably/utils.py | 6 ++-- 6 files changed, 42 insertions(+), 52 deletions(-) diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 23fd2545..23d2f1a3 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -336,7 +336,7 @@ def test_client_id_precedence(self): self.assertEqual(ably.auth.client_id, client_id) channel = ably.channels[ - self.protocol_channel_name('test_client_id_precedence')] + self.get_channel_name('test_client_id_precedence')] channel.publish('test', 'data') self.assertEqual(channel.history().items[0].client_id, client_id) @@ -378,7 +378,7 @@ def test_with_key(self): tls_port=test_vars["tls_port"], tls=test_vars["tls"], use_binary_protocol=self.use_binary_protocol) - channel = self.protocol_channel_name('test_request_token_with_key') + channel = self.get_channel_name('test_request_token_with_key') ably.channels[channel].publish('event', 'foo') diff --git a/test/ably/restchannelhistory_test.py b/test/ably/restchannelhistory_test.py index 675d1b04..cdf796c0 100644 --- a/test/ably/restchannelhistory_test.py +++ b/test/ably/restchannelhistory_test.py @@ -5,7 +5,6 @@ import responses import six -import msgpack from six.moves import range from ably import AblyException @@ -36,7 +35,7 @@ def per_protocol_setup(self, use_binary_protocol): def test_channel_history_types(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_types')] + self.get_channel_name('persisted:channelhistory_types')] history0.publish('history0', six.u('This is a string message payload')) history0.publish('history1', b'This is a byte[] message payload') @@ -75,7 +74,7 @@ def test_channel_history_types(self): def test_channel_history_multi_50_forwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_multi_50_f')] + self.get_channel_name('persisted:channelhistory_multi_50_f')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -83,17 +82,15 @@ def test_channel_history_multi_50_forwards(self): history = history0.history(direction='forwards') self.assertIsNotNone(history) messages = history.items - self.assertEqual(50, len(messages), - msg="Expected 50 messages") + assert len(messages) == 50, "Expected 50 messages" message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(50)] - self.assertEqual(expected_messages, messages, - msg='Expect messages in forward order') + assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_multi_50_backwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_multi_50_b')] + self.get_channel_name('persisted:channelhistory_multi_50_b')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -154,7 +151,7 @@ def test_channel_history_max_limit_is_1000(self): def test_channel_history_limit_forwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_limit_f')] + self.get_channel_name('persisted:channelhistory_limit_f')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -162,18 +159,15 @@ def test_channel_history_limit_forwards(self): history = history0.history(direction='forwards', limit=25) self.assertIsNotNone(history) messages = history.items - self.assertEqual(25, len(messages), - msg="Expected 25 messages") + assert len(messages) == 25, "Expected 25 messages" message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(25)] - - self.assertEqual(expected_messages, messages, - msg='Expect messages in forward order') + assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_limit_backwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_limit_b')] + self.get_channel_name('persisted:channelhistory_limit_b')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -181,18 +175,15 @@ def test_channel_history_limit_backwards(self): history = history0.history(direction='backwards', limit=25) self.assertIsNotNone(history) messages = history.items - self.assertEqual(25, len(messages), - msg="Expected 25 messages") + assert len(messages) == 25, "Expected 25 messages" message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(49, 24, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expect messages in forward order') + assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_time_forwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_time_f')] + self.get_channel_name('persisted:channelhistory_time_f')] for i in range(20): history0.publish('history%d' % i, str(i)) @@ -221,7 +212,7 @@ def test_channel_history_time_forwards(self): def test_channel_history_time_backwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_time_b')] + self.get_channel_name('persisted:channelhistory_time_b')] for i in range(20): history0.publish('history%d' % i, str(i)) @@ -250,7 +241,7 @@ def test_channel_history_time_backwards(self): def test_channel_history_paginate_forwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_paginate_f')] + self.get_channel_name('persisted:channelhistory_paginate_f')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -290,7 +281,7 @@ def test_channel_history_paginate_forwards(self): def test_channel_history_paginate_backwards(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_paginate_b')] + self.get_channel_name('persisted:channelhistory_paginate_b')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -330,7 +321,7 @@ def test_channel_history_paginate_backwards(self): def test_channel_history_paginate_forwards_first(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_paginate_first_f')] + self.get_channel_name('persisted:channelhistory_paginate_first_f')] for i in range(50): history0.publish('history%d' % i, str(i)) @@ -370,7 +361,7 @@ def test_channel_history_paginate_forwards_first(self): def test_channel_history_paginate_backwards_rel_first(self): history0 = self.ably.channels[ - self.protocol_channel_name('persisted:channelhistory_paginate_first_b')] + self.get_channel_name('persisted:channelhistory_paginate_first_b')] for i in range(50): history0.publish('history%d' % i, str(i)) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index a496edd0..59a39bbc 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -48,7 +48,7 @@ def per_protocol_setup(self, use_binary_protocol): def test_publish_various_datatypes_text(self): publish0 = self.ably.channels[ - self.protocol_channel_name('persisted:publish0')] + self.get_channel_name('persisted:publish0')] publish0.publish("publish0", six.u("This is a string message payload")) publish0.publish("publish1", b"This is a byte[] message payload") @@ -86,7 +86,7 @@ def test_unsuporsed_payload_must_raise_exception(self): def test_publish_message_list(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:message_list_channel')] + self.get_channel_name('persisted:message_list_channel')] expected_messages = [Message("name-{}".format(i), str(i)) for i in range(3)] @@ -105,7 +105,7 @@ def test_publish_message_list(self): def test_message_list_generate_one_request(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:message_list_channel_one_request')] + self.get_channel_name('persisted:message_list_channel_one_request')] expected_messages = [Message("name-{}".format(i), six.text_type(i)) for i in range(3)] @@ -141,7 +141,7 @@ def test_publish_error(self): def test_publish_message_null_name(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:message_null_name_channel')] + self.get_channel_name('persisted:message_null_name_channel')] data = "String message" channel.publish(name=None, data=data) @@ -158,7 +158,7 @@ def test_publish_message_null_name(self): def test_publish_message_null_data(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:message_null_data_channel')] + self.get_channel_name('persisted:message_null_data_channel')] name = "Test name" channel.publish(name=name, data=None) @@ -175,7 +175,7 @@ def test_publish_message_null_data(self): def test_publish_message_null_name_and_data(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:null_name_and_data_channel')] + self.get_channel_name('persisted:null_name_and_data_channel')] channel.publish(name=None, data=None) channel.publish() @@ -193,7 +193,7 @@ def test_publish_message_null_name_and_data(self): def test_publish_message_null_name_and_data_keys_arent_sent(self): channel = self.ably.channels[ - self.protocol_channel_name('persisted:null_name_and_data_keys_arent_sent_channel')] + self.get_channel_name('persisted:null_name_and_data_keys_arent_sent_channel')] with mock.patch('ably.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: @@ -218,7 +218,7 @@ def test_publish_message_null_name_and_data_keys_arent_sent(self): def test_message_attr(self): publish0 = self.ably.channels[ - self.protocol_channel_name('persisted:publish_message_attr')] + self.get_channel_name('persisted:publish_message_attr')] messages = [Message('publish', {"test": "This is a JSONObject message payload"}, @@ -243,7 +243,7 @@ def test_token_is_bound_to_options_client_id_after_publish(self): # created after message publish and will have client_id channel = self.ably_with_client_id.channels[ - self.protocol_channel_name('persisted:restricted_to_client_id')] + self.get_channel_name('persisted:restricted_to_client_id')] channel.publish(name='publish', data='test') # defined after publish @@ -254,7 +254,7 @@ def test_token_is_bound_to_options_client_id_after_publish(self): def test_publish_message_without_client_id_on_identified_client(self): channel = self.ably_with_client_id.channels[ - self.protocol_channel_name('persisted:no_client_id_identified_client')] + self.get_channel_name('persisted:no_client_id_identified_client')] with mock.patch('ably.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: @@ -289,7 +289,7 @@ def test_publish_message_without_client_id_on_identified_client(self): def test_publish_message_with_client_id_on_identified_client(self): # works if same channel = self.ably_with_client_id.channels[ - self.protocol_channel_name('persisted:with_client_id_identified_client')] + self.get_channel_name('persisted:with_client_id_identified_client')] channel.publish(name='publish', data='test', client_id=self.ably_with_client_id.client_id) @@ -316,7 +316,7 @@ def test_publish_message_with_wrong_client_id_on_implicit_identified_client(self tls=test_vars["tls"], use_binary_protocol=self.use_binary_protocol) channel = new_ably.channels[ - self.protocol_channel_name('persisted:wrong_client_id_implicit_client')] + self.get_channel_name('persisted:wrong_client_id_implicit_client')] with self.assertRaises(AblyException) as cm: channel.publish(name='publish', data='test', @@ -338,7 +338,7 @@ def test_wildcard_client_id_can_publish_as_others(self): self.assertEqual(wildcard_ably.auth.client_id, '*') channel = wildcard_ably.channels[ - self.protocol_channel_name('persisted:wildcard_client_id')] + self.get_channel_name('persisted:wildcard_client_id')] channel.publish(name='publish1', data='no client_id') some_client_id = uuid.uuid4().hex channel.publish(name='publish2', data='some client_id', @@ -367,7 +367,7 @@ def test_invalid_connection_key(self): # TM2i, RSL6a2, RSL1h def test_publish_extras(self): channel = self.ably.channels[ - self.protocol_channel_name('canpublish:extras_channel')] + self.get_channel_name('canpublish:extras_channel')] extras = { 'push': { 'notification': {"title": "Testing"}, @@ -384,7 +384,7 @@ def test_publish_extras(self): # RSL6a1 def test_interoperability(self): - name = self.protocol_channel_name('persisted:interoperability_channel') + name = self.get_channel_name('persisted:interoperability_channel') channel = self.ably.channels[name] url = 'https://%s/channels/%s/messages' % (test_vars["host"], name) diff --git a/test/ably/restcrypto_test.py b/test/ably/restcrypto_test.py index 2e741ea4..7b8fb5b2 100644 --- a/test/ably/restcrypto_test.py +++ b/test/ably/restcrypto_test.py @@ -70,7 +70,7 @@ def test_cbc_channel_cipher(self): self.assertEqual(expected_ciphertext, actual_ciphertext) def test_crypto_publish(self): - channel_name = self.protocol_channel_name('persisted:crypto_publish_text') + channel_name = self.get_channel_name('persisted:crypto_publish_text') publish0 = self.ably.channels.get(channel_name, cipher={'key': generate_random_key()}) publish0.publish("publish3", six.u("This is a string message payload")) @@ -134,7 +134,7 @@ def test_crypto_publish_256(self): msg="Expect publish6 to be expected JSONObject") def test_crypto_publish_key_mismatch(self): - channel_name = self.protocol_channel_name('persisted:crypto_publish_key_mismatch') + channel_name = self.get_channel_name('persisted:crypto_publish_key_mismatch') publish0 = self.ably.channels.get(channel_name, cipher={'key': generate_random_key()}) @@ -161,7 +161,7 @@ def test_crypto_publish_key_mismatch(self): the_exception.message.startswith("UnicodeDecodeError: 'utf-8'")) def test_crypto_send_unencrypted(self): - channel_name = self.protocol_channel_name('persisted:crypto_send_unencrypted') + channel_name = self.get_channel_name('persisted:crypto_send_unencrypted') publish0 = self.ably.channels[channel_name] publish0.publish("publish3", six.u("This is a string message payload")) @@ -193,7 +193,7 @@ def test_crypto_send_unencrypted(self): msg="Expect publish6 to be expected JSONObject") def test_crypto_encrypted_unhandled(self): - channel_name = self.protocol_channel_name('persisted:crypto_send_encrypted_unhandled') + channel_name = self.get_channel_name('persisted:crypto_send_encrypted_unhandled') key = six.b('0123456789abcdef') data = six.u('foobar') publish0 = self.ably.channels.get(channel_name, cipher={'key': key}) diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index 2ad2be6b..de557d13 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -6,7 +6,6 @@ from test.ably.restsetup import RestSetup from test.ably.utils import BaseTestCase from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol -from test.ably.utils import random_string test_vars = RestSetup.get_test_vars() @@ -24,7 +23,7 @@ def setUpClass(cls): tls=test_vars["tls"]) # Populate the channel (using the new api) - cls.channel = random_string(8) + cls.channel = cls.get_channel_name() cls.path = '/channels/%s/messages' % cls.channel for i in range(20): body = {'name': 'event%s' % i, 'data': 'lorem ipsum %s' % i} diff --git a/test/ably/utils.py b/test/ably/utils.py index db0d5ea6..1f60384a 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -16,9 +16,9 @@ def responses_add_empty_msg_pack(self, url, method=responses.GET): responses.add(responses.GET, url, body=msgpack.packb({}), content_type='application/x-msgpack') - def protocol_channel_name(self, prefix=''): - suffix = '_bin' if self.use_binary_protocol else '_text' - return prefix + random_string(8) + suffix + @classmethod + def get_channel_name(cls, prefix=''): + return prefix + random_string(10) def assert_responses_type(protocol): From 203849320a12aa9f230832e3437d8cf7cf1868a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 22 Jun 2018 10:45:45 +0200 Subject: [PATCH 11/85] Fix tests, don't send deviceSecret anymore --- ably/types/device.py | 9 ++------- test/ably/restpush_test.py | 6 +----- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/ably/types/device.py b/ably/types/device.py index 6cb9cbdf..60bda43c 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -6,8 +6,8 @@ class DeviceDetails(object): def __init__(self, id, clientId=None, formFactor=None, metadata=None, - platform=None, push=None, updateToken=None, - deviceSecret=None, appId=None, deviceIdentityToken=None): + platform=None, push=None, updateToken=None, appId=None, + deviceIdentityToken=None): if push: recipient = push.get('recipient') @@ -29,7 +29,6 @@ def __init__(self, id, clientId=None, formFactor=None, metadata=None, self.__platform = platform self.__push = push self.__update_token = updateToken - self.__device_secret = deviceSecret self.__app_id = appId self.__device_identity_token = deviceIdentityToken @@ -61,10 +60,6 @@ def push(self): def update_token(self): return self.__update_token - @property - def device_secret(self): - return self.__device_secret - @property def app_id(self): return self.__app_id diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 71ad20e8..e0b5ba56 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -78,7 +78,6 @@ def test_admin_device_registrations_get(self): 'deviceToken': DEVICE_TOKEN } }, - 'deviceSecret': random_string(12), } self.__save(data) @@ -87,7 +86,6 @@ def test_admin_device_registrations_get(self): assert device_details.id == device_id assert device_details.platform == data['platform'] assert device_details.form_factor == data['formFactor'] - assert device_details.device_secret == data['deviceSecret'] # RSH1b2 def test_admin_device_registrations_list(self): @@ -106,7 +104,6 @@ def test_admin_device_registrations_list(self): 'deviceToken': DEVICE_TOKEN, } }, - 'deviceSecret': random_string(12), } self.__save(data) datas.append(data) @@ -149,7 +146,6 @@ def test_admin_device_registrations_save(self): 'deviceToken': DEVICE_TOKEN, } }, - 'deviceSecret': random_string(12), } # Create @@ -169,4 +165,4 @@ def test_admin_device_registrations_save(self): # Fail with pytest.raises(AblyException): - self.__save(new_dict(data, deviceSecret=random_string(12))) + self.__save(new_dict(data, push={'color': 'red'})) From d60d90857400c27115a174d5b7cb4a0b17d54d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 22 Jun 2018 11:43:53 +0200 Subject: [PATCH 12/85] Travis: don't use tox, don't run tests in parallel Sometimes, when the sandbox is under heavy load, we get timeout errors. Be easy on the sandbox by not running the tests in parallel. The speed we get by running the tests in parallel does not compensate the troubling random errors. Also, don't need to use tox in travis. Also, run flake8 only over the project files. --- .gitignore | 1 + .travis.yml | 4 ++-- tox.ini | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 404af9e3..d902fd24 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ pip-log.txt # Unit test / coverage reports .cache .coverage +/.pytest_cache/ .tox /htmlcov/ diff --git a/.travis.yml b/.travis.yml index 90414f59..1d79987f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ python: - "3.6" sudo: false install: - - travis_retry pip install tox-travis + - travis_retry pip install -r requirements-test.txt script: - - tox + - py.test --tb=short test after_success: - "if [ $TRAVIS_PYTHON_VERSION == '2.7' ]; then pip install coveralls; coveralls; fi" diff --git a/tox.ini b/tox.ini index 57f6cd64..790fb31b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,6 @@ envlist = flake8 [testenv] -passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH - deps = -rrequirements-test.txt @@ -14,4 +12,4 @@ commands = [testenv:flake8] commands = - flake8 + flake8 setup.py ably test From 3b8fa2e24cbf76eca5c1f036043659baa117ae4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 23 May 2018 10:00:47 +0200 Subject: [PATCH 13/85] RHS1b4 New push.admin.device_registrations.remove --- ably/http/http.py | 4 ++++ ably/rest/push.py | 9 +++++++++ test/ably/restpush_test.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/ably/http/http.py b/ably/http/http.py index 456b20bd..05c138dd 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -94,6 +94,10 @@ def to_native(self): else: raise ValueError("Unsuported content type") + @property + def response(self): + return self.__response + def __getattr__(self, attr): return getattr(self.__response, attr) diff --git a/ably/rest/push.py b/ably/rest/push.py index 60f34f5a..a5c3a712 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -95,3 +95,12 @@ def save(self, device): response = self.ably.http.put(path, body=device) details = response.to_native() return DeviceDetails(**details) + + def remove(self, device_id): + """Deletes the registered device identified by the given device id. + + :Parameters: + - `device_id`: the id of the device + """ + path = '/push/deviceRegistrations/%s' % device_id + return self.ably.http.delete(path) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index e0b5ba56..35e532b7 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -166,3 +166,32 @@ def test_admin_device_registrations_save(self): # Fail with pytest.raises(AblyException): self.__save(new_dict(data, push={'color': 'red'})) + + # RSH1b4 + def test_admin_device_registrations_remove(self): + remove = self.ably.push.admin.device_registrations.remove + get = self.ably.push.admin.device_registrations.get + + # Save + device_id = random_string(26, string.ascii_uppercase + string.digits) + data = { + 'id': device_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': DEVICE_TOKEN + } + }, + } + self.__save(data) + + # Remove + assert get(device_id).id == device_id # Exists + assert remove(device_id).status_code == 204 + with pytest.raises(AblyException): get(device_id) # Doesn't exist + + # Remove again, it doesn't fail + response = remove(device_id) + assert response.status_code == 204 From 969fdd619a865eef58f55408f3e0a5404b67e071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 23 May 2018 10:35:46 +0200 Subject: [PATCH 14/85] RHS1b5 New push.admin.device_registrations.remove_where --- ably/rest/push.py | 9 ++++++ test/ably/restpush_test.py | 60 +++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index a5c3a712..3361e90d 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -104,3 +104,12 @@ def remove(self, device_id): """ path = '/push/deviceRegistrations/%s' % device_id return self.ably.http.delete(path) + + def remove_where(self, **params): + """Deletes the registered devices identified by the given parameters. + + :Parameters: + - `**params`: the parameters that identify the devices to remove + """ + path = '/push/deviceRegistrations' + format_params(params) + return self.ably.http.delete(path) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 35e532b7..713f78e5 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -1,4 +1,5 @@ import string +import time import pytest import six @@ -42,6 +43,9 @@ def __save(self, data): self.count += 1 return result + def get_device_id(self): + return random_string(26, string.ascii_uppercase + string.digits) + # RSH1a def test_admin_publish(self): recipient = {'clientId': 'ablyChannel'} @@ -67,7 +71,7 @@ def test_admin_device_registrations_get(self): get('not-found') # Save - device_id = random_string(26, string.ascii_uppercase + string.digits) + device_id = self.get_device_id() data = { 'id': device_id, 'platform': 'ios', @@ -91,7 +95,7 @@ def test_admin_device_registrations_get(self): def test_admin_device_registrations_list(self): datas = [] for i in range(10): - device_id = random_string(26, string.ascii_uppercase + string.digits) + device_id = self.get_device_id() client_id = random_string(12) data = { 'id': device_id, @@ -124,7 +128,7 @@ def test_admin_device_registrations_list(self): response = self.ably.push.admin.device_registrations.list(deviceId=first['id']) assert len(response.items) == 1 response = self.ably.push.admin.device_registrations.list( - deviceId=random_string(26, string.ascii_uppercase + string.digits)) + deviceId=self.get_device_id()) assert len(response.items) == 0 # Filter by client id @@ -135,7 +139,8 @@ def test_admin_device_registrations_list(self): # RSH1b3 def test_admin_device_registrations_save(self): - device_id = random_string(26, string.ascii_uppercase + string.digits) + device_id = self.get_device_id() + data = { 'id': device_id, 'platform': 'ios', @@ -173,7 +178,7 @@ def test_admin_device_registrations_remove(self): get = self.ably.push.admin.device_registrations.get # Save - device_id = random_string(26, string.ascii_uppercase + string.digits) + device_id = self.get_device_id() data = { 'id': device_id, 'platform': 'ios', @@ -193,5 +198,46 @@ def test_admin_device_registrations_remove(self): with pytest.raises(AblyException): get(device_id) # Doesn't exist # Remove again, it doesn't fail - response = remove(device_id) - assert response.status_code == 204 + assert remove(device_id).status_code == 204 + + # RSH1b5 + def test_admin_device_registrations_remove_where(self): + remove_where = self.ably.push.admin.device_registrations.remove_where + get = self.ably.push.admin.device_registrations.get + + # Save + datas = [] + for i in range(5): + device_id = self.get_device_id() + client_id = random_string(12) + data = { + 'id': device_id, + 'clientId': client_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': DEVICE_TOKEN + } + }, + } + self.__save(data) + datas.append(data) + + # Remove by device id + device_id = datas[0]['id'] + assert get(device_id).id == device_id # Exists + assert remove_where(deviceId=device_id).status_code == 204 + with pytest.raises(AblyException): get(device_id) # Doesn't exist + + # Remove by client id + device_id = datas[1]['id'] + client_id = datas[1]['clientId'] + assert get(device_id).id == device_id # Exists + assert remove_where(clientId=client_id).status_code == 204 + time.sleep(1) # Deletion is async: wait a little bit + with pytest.raises(AblyException): get(device_id) # Doesn't exist + + # Remove with no matching params + assert remove_where(clientId=data['clientId']).status_code == 204 From 1d68dbb3b9a21a881371b2ed1182c33a8e65f4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 23 May 2018 16:55:47 +0200 Subject: [PATCH 15/85] RSH1c3 New push.admin.channel_subscriptions.save --- ably/__init__.py | 1 + ably/rest/push.py | 30 ++++++++++++++++++ ably/types/channelsubscription.py | 49 +++++++++++++++++++++++++++++ ably/types/utils.py | 11 +++++++ test/ably/restpush_test.py | 51 +++++++++++++++++++++++++++++-- 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 ably/types/channelsubscription.py create mode 100644 ably/types/utils.py diff --git a/ably/__init__.py b/ably/__init__.py index fc72ac85..9fcf7c69 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -23,6 +23,7 @@ def createLock(self): from ably.rest.auth import Auth from ably.rest.push import Push from ably.types.capability import Capability +from ably.types.channelsubscription import PushChannelSubscription from ably.types.device import DeviceDetails from ably.types.options import Options from ably.util.crypto import CipherParams diff --git a/ably/rest/push.py b/ably/rest/push.py index 3361e90d..752a699d 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,5 +1,6 @@ from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.device import DeviceDetails, make_device_details_response_processor +from ably.types.channelsubscription import PushChannelSubscription class Push(object): @@ -17,6 +18,7 @@ class PushAdmin(object): def __init__(self, ably): self.__ably = ably self.__device_registrations = PushDeviceRegistrations(ably) + self.__channel_subscriptions = PushChannelSubscriptions(ably) @property def ably(self): @@ -26,6 +28,10 @@ def ably(self): def device_registrations(self): return self.__device_registrations + @property + def channel_subscriptions(self): + return self.__channel_subscriptions + def publish(self, recipient, data, timeout=None): """Publish a push notification to a single device. @@ -113,3 +119,27 @@ def remove_where(self, **params): """ path = '/push/deviceRegistrations' + format_params(params) return self.ably.http.delete(path) + + +class PushChannelSubscriptions(object): + + def __init__(self, ably): + self.__ably = ably + + @property + def ably(self): + return self.__ably + + def save(self, subscription): + """Creates or updates the subscription. Returns a + PushChannelSubscription object. + + :Parameters: + - `subscription`: a dictionary with the subscription information + """ + subscription = PushChannelSubscription.factory(subscription) + path = '/push/channelSubscriptions' + body = subscription.as_dict() + response = self.ably.http.post(path, body=body) + obj = response.to_native() + return PushChannelSubscription.from_dict(obj) diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py new file mode 100644 index 00000000..a18bc359 --- /dev/null +++ b/ably/types/channelsubscription.py @@ -0,0 +1,49 @@ +from .utils import camel_to_snake, snake_to_camel + + +class PushChannelSubscription(object): + + def __init__(self, channel, device_id=None, client_id=None, app_id=None): + if not device_id and not client_id: + raise ValueError('missing expected device or client id') + + if device_id and client_id: + raise ValueError('both device and client id given, only one expected') + + self.__channel = channel + self.__device_id = device_id + self.__client_id = client_id + self.__app_id = app_id + + @property + def channel(self): + return self.__channel + + @property + def device_id(self): + return self.__device_id + + @property + def client_id(self): + return self.__client_id + + @property + def app_id(self): + return self.__app_id + + def as_dict(self): + keys = ['channel', 'device_id', 'client_id', 'app_id'] + obj = {snake_to_camel(key): getattr(self, key) for key in keys} + return obj + + @classmethod + def from_dict(self, obj): + obj = {camel_to_snake(key): value for key, value in obj.items()} + return self(**obj) + + @classmethod + def factory(self, subscription): + if isinstance(subscription, self): + return subscription + + return self.from_dict(subscription) diff --git a/ably/types/utils.py b/ably/types/utils.py new file mode 100644 index 00000000..b3d83c08 --- /dev/null +++ b/ably/types/utils.py @@ -0,0 +1,11 @@ +import re + +def camel_to_snake(name, first_cap_re = re.compile('(.)([A-Z][a-z]+)')): + return first_cap_re.sub(r'\1_\2', name).lower() + +def snake_to_camel(name): + name = name.split('_') + for i in range(1, len(name)): + name[i] = name[i].title() + + return ''.join(name) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 713f78e5..0527875f 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -4,7 +4,8 @@ import pytest import six -from ably import AblyRest, AblyException, DeviceDetails +from ably import AblyRest, AblyException, AblyAuthException +from ably import DeviceDetails, PushChannelSubscription from ably.http.paginatedresult import PaginatedResult from test.ably.restsetup import RestSetup @@ -240,4 +241,50 @@ def test_admin_device_registrations_remove_where(self): with pytest.raises(AblyException): get(device_id) # Doesn't exist # Remove with no matching params - assert remove_where(clientId=data['clientId']).status_code == 204 + assert remove_where(clientId=client_id).status_code == 204 + + # RSH1c3 + def test_admin_channel_subscriptions_save(self): + save = self.ably.push.admin.channel_subscriptions.save + + # Register device + device_id = self.get_device_id() + data = { + 'id': device_id, + 'platform': 'ios', + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': DEVICE_TOKEN + } + }, + } + self.__save(data) + + # Subscribe + channel = 'canpublish:test' + subscription = PushChannelSubscription(channel, device_id=device_id) + subscription = save(subscription) + assert type(subscription) is PushChannelSubscription + assert subscription.channel == channel + assert subscription.device_id == device_id + + # Update + channel = 'canpublish:test' + subscription = PushChannelSubscription(channel, device_id=device_id) + subscription = save(subscription) + assert type(subscription) is PushChannelSubscription + assert subscription.channel == channel + assert subscription.device_id == device_id + + # Failures + client_id = random_string(12) + with pytest.raises(ValueError): + PushChannelSubscription(channel, device_id=device_id, client_id=client_id) + + subscription = PushChannelSubscription('notallowed', device_id=device_id) + with pytest.raises(AblyAuthException): save(subscription) + + subscription = PushChannelSubscription(channel, device_id='notregistered') + with pytest.raises(AblyException): save(subscription) From a1f708a98efe971fa57384b0388ced81d5f98ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 25 Jun 2018 15:58:52 +0200 Subject: [PATCH 16/85] push: refactor tests, reduce dup code --- test/ably/restpush_test.py | 264 ++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 152 deletions(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 0527875f..f2f067f3 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -1,3 +1,4 @@ +import random import string import time @@ -21,8 +22,6 @@ @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestPush(BaseTestCase): - count = 0 # Number of devices registered - @classmethod def setUpClass(self): self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], @@ -31,21 +30,78 @@ def setUpClass(self): tls_port=test_vars["tls_port"], tls=test_vars["tls"]) + # Register several devices for later use + self.devices = {} + for i in range(10): + self.save_device() + def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @classmethod - def __save(self, data): + def get_client_id(self): + return random_string(12) + + @classmethod + def get_device_id(self): + return random_string(26, string.ascii_uppercase + string.digits) + + @classmethod + def gen_device_data(self, data=None, **kw): + if data is None: + data = { + 'id': self.get_device_id(), + 'clientId': self.get_client_id(), + 'platform': random.choice(['android', 'ios']), + 'formFactor': 'phone', + 'push': { + 'recipient': { + 'transportType': 'apns', + 'deviceToken': DEVICE_TOKEN, + } + }, + } + else: + data = data.copy() + + data.update(kw) + return data + + @classmethod + def save_device(self, data=None, **kw): """ - Wrapps calls to save, to keep a count on the numer of devices - registered. + Helper method to register a device, to not have this code repeated + everywhere. Returns the input dict that was sent to Ably, and the + device details returned by Ably. """ - result = self.ably.push.admin.device_registrations.save(data) - self.count += 1 + data = self.gen_device_data(data, **kw) + device = self.ably.push.admin.device_registrations.save(data) + self.devices[device.id] = device + return device + + @classmethod + def remove_device(self, device_id): + result = self.ably.push.admin.device_registrations.remove(device_id) + self.devices.pop(device_id, None) return result - def get_device_id(self): - return random_string(26, string.ascii_uppercase + string.digits) + @classmethod + def remove_device_where(self, **kw): + remove_where = self.ably.push.admin.device_registrations.remove_where + result = remove_where(**kw) + + aux = {'deviceId': 'id', 'clientId': 'client_id'} + for device in list(self.devices.values()): + for key, value in kw.items(): + key = aux[key] + if getattr(device, key) == value: + del self.devices[device.id] + + return result + + def get_device(self): + key = random.choice(list(self.devices.keys())) + return self.devices[key] # RSH1a def test_admin_publish(self): @@ -71,219 +127,123 @@ def test_admin_device_registrations_get(self): with pytest.raises(AblyException): get('not-found') - # Save - device_id = self.get_device_id() - data = { - 'id': device_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN - } - }, - } - self.__save(data) - # Found - device_details = get(device_id) - assert device_details.id == device_id - assert device_details.platform == data['platform'] - assert device_details.form_factor == data['formFactor'] + device = self.get_device() + device_details = get(device.id) + assert device_details.id == device.id + assert device_details.platform == device.platform + assert device_details.form_factor == device.form_factor # RSH1b2 def test_admin_device_registrations_list(self): - datas = [] - for i in range(10): - device_id = self.get_device_id() - client_id = random_string(12) - data = { - 'id': device_id, - 'clientId': client_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN, - } - }, - } - self.__save(data) - datas.append(data) + list_devices = self.ably.push.admin.device_registrations.list - response = self.ably.push.admin.device_registrations.list() + response = list_devices() assert type(response) is PaginatedResult assert type(response.items) is list assert type(response.items[0]) is DeviceDetails # limit - response = self.ably.push.admin.device_registrations.list(limit=5000) - assert len(response.items) == self.count - response = self.ably.push.admin.device_registrations.list(limit=2) - assert len(response.items) == 2 + assert len(list_devices(limit=5000).items) == len(self.devices) + assert len(list_devices(limit=2).items) == 2 # Filter by device id - first = datas[0] - response = self.ably.push.admin.device_registrations.list(deviceId=first['id']) - assert len(response.items) == 1 - response = self.ably.push.admin.device_registrations.list( - deviceId=self.get_device_id()) - assert len(response.items) == 0 + device = self.get_device() + assert len(list_devices(deviceId=device.id).items) == 1 + assert len(list_devices(deviceId=self.get_device_id()).items) == 0 # Filter by client id - response = self.ably.push.admin.device_registrations.list(clientId=first['clientId']) - assert len(response.items) == 1 - response = self.ably.push.admin.device_registrations.list(clientId=random_string(12)) - assert len(response.items) == 0 + assert len(list_devices(clientId=device.client_id).items) == 1 + assert len(list_devices(clientId=self.get_client_id()).items) == 0 # RSH1b3 def test_admin_device_registrations_save(self): - device_id = self.get_device_id() - - data = { - 'id': device_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN, - } - }, - } - # Create - device_details = self.__save(data) - assert type(device_details) is DeviceDetails + data = self.gen_device_data() + device = self.save_device(data) + assert type(device) is DeviceDetails # Update - self.__save(new_dict(data, formFactor='tablet')) + self.save_device(data, formFactor='tablet') # Invalid values with pytest.raises(ValueError): - self.__save(new_dict(data, push={'recipient': new_dict(data['push']['recipient'], transportType='xyz')})) + push = {'recipient': new_dict(data['push']['recipient'], transportType='xyz')} + self.save_device(data, push=push) with pytest.raises(ValueError): - self.__save(new_dict(data, platform='native')) + self.save_device(data, platform='native') with pytest.raises(ValueError): - self.__save(new_dict(data, formFactor='fridge')) + self.save_device(data, formFactor='fridge') # Fail with pytest.raises(AblyException): - self.__save(new_dict(data, push={'color': 'red'})) + self.save_device(data, push={'color': 'red'}) # RSH1b4 def test_admin_device_registrations_remove(self): - remove = self.ably.push.admin.device_registrations.remove get = self.ably.push.admin.device_registrations.get - # Save - device_id = self.get_device_id() - data = { - 'id': device_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN - } - }, - } - self.__save(data) + device = self.get_device() # Remove - assert get(device_id).id == device_id # Exists - assert remove(device_id).status_code == 204 - with pytest.raises(AblyException): get(device_id) # Doesn't exist + assert get(device.id).id == device.id # Exists + assert self.remove_device(device.id).status_code == 204 + with pytest.raises(AblyException): get(device.id) # Doesn't exist # Remove again, it doesn't fail - assert remove(device_id).status_code == 204 + assert self.remove_device(device.id).status_code == 204 # RSH1b5 def test_admin_device_registrations_remove_where(self): - remove_where = self.ably.push.admin.device_registrations.remove_where get = self.ably.push.admin.device_registrations.get - # Save - datas = [] - for i in range(5): - device_id = self.get_device_id() - client_id = random_string(12) - data = { - 'id': device_id, - 'clientId': client_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN - } - }, - } - self.__save(data) - datas.append(data) - # Remove by device id - device_id = datas[0]['id'] - assert get(device_id).id == device_id # Exists - assert remove_where(deviceId=device_id).status_code == 204 - with pytest.raises(AblyException): get(device_id) # Doesn't exist + device = self.get_device() + assert get(device.id).id == device.id # Exists + assert self.remove_device_where(deviceId=device.id).status_code == 204 + with pytest.raises(AblyException): get(device.id) # Doesn't exist # Remove by client id - device_id = datas[1]['id'] - client_id = datas[1]['clientId'] - assert get(device_id).id == device_id # Exists - assert remove_where(clientId=client_id).status_code == 204 + device = self.get_device() + assert get(device.id).id == device.id # Exists + assert self.remove_device_where(clientId=device.client_id).status_code == 204 time.sleep(1) # Deletion is async: wait a little bit - with pytest.raises(AblyException): get(device_id) # Doesn't exist + with pytest.raises(AblyException): get(device.id) # Doesn't exist # Remove with no matching params - assert remove_where(clientId=client_id).status_code == 204 + assert self.remove_device_where(clientId=device.client_id).status_code == 204 # RSH1c3 def test_admin_channel_subscriptions_save(self): save = self.ably.push.admin.channel_subscriptions.save # Register device - device_id = self.get_device_id() - data = { - 'id': device_id, - 'platform': 'ios', - 'formFactor': 'phone', - 'push': { - 'recipient': { - 'transportType': 'apns', - 'deviceToken': DEVICE_TOKEN - } - }, - } - self.__save(data) + device = self.get_device() # Subscribe channel = 'canpublish:test' - subscription = PushChannelSubscription(channel, device_id=device_id) + subscription = PushChannelSubscription(channel, device_id=device.id) subscription = save(subscription) assert type(subscription) is PushChannelSubscription assert subscription.channel == channel - assert subscription.device_id == device_id + assert subscription.device_id == device.id + assert subscription.client_id is None # Update channel = 'canpublish:test' - subscription = PushChannelSubscription(channel, device_id=device_id) + subscription = PushChannelSubscription(channel, device_id=device.id) subscription = save(subscription) assert type(subscription) is PushChannelSubscription assert subscription.channel == channel - assert subscription.device_id == device_id + assert subscription.device_id == device.id + assert subscription.client_id is None # Failures - client_id = random_string(12) + client_id = self.get_client_id() with pytest.raises(ValueError): - PushChannelSubscription(channel, device_id=device_id, client_id=client_id) + PushChannelSubscription(channel, device_id=device.id, client_id=client_id) - subscription = PushChannelSubscription('notallowed', device_id=device_id) + subscription = PushChannelSubscription('notallowed', device_id=device.id) with pytest.raises(AblyAuthException): save(subscription) subscription = PushChannelSubscription(channel, device_id='notregistered') From 51554917218b4485c7387c4f6c5a0ddf61ec6aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 15:45:34 +0200 Subject: [PATCH 17/85] Fixing remove_where tests The call is async, wait a little bit longer before giving up. --- test/ably/restpush_test.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index f2f067f3..1eaa1bf2 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -207,8 +207,11 @@ def test_admin_device_registrations_remove_where(self): device = self.get_device() assert get(device.id).id == device.id # Exists assert self.remove_device_where(clientId=device.client_id).status_code == 204 - time.sleep(1) # Deletion is async: wait a little bit - with pytest.raises(AblyException): get(device.id) # Doesn't exist + # Doesn't exist (Deletion is async: wait up to a few seconds before giving up) + with pytest.raises(AblyException): + for i in range(5): + time.sleep(1) + get(device.id) # Remove with no matching params assert self.remove_device_where(clientId=device.client_id).status_code == 204 From 55b424eb4a3003e34b1fd64dc3506edf58a9ba7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 16:58:49 +0200 Subject: [PATCH 18/85] flake8 fixes, run flake8 in travis --- .travis.yml | 4 +- ably/http/paginatedresult.py | 2 +- ably/rest/push.py | 13 +++--- ably/types/channelsubscription.py | 10 ++-- ably/types/device.py | 48 ++++++++++++++----- ably/types/mixins.py | 5 +- requirements-test.txt | 3 +- test/ably/restauth_test.py | 2 +- test/ably/restpush_test.py | 78 +++++++++++++++++-------------- test/ably/restrequest_test.py | 1 - 10 files changed, 98 insertions(+), 68 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d79987f..bef329d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ sudo: false install: - travis_retry pip install -r requirements-test.txt script: - - py.test --tb=short test + - py.test --tb=short --flake8 after_success: - - "if [ $TRAVIS_PYTHON_VERSION == '2.7' ]; then pip install coveralls; coveralls; fi" + - "if [ $TRAVIS_PYTHON_VERSION == '3.6' ]; then pip install coveralls; coveralls; fi" diff --git a/ably/http/paginatedresult.py b/ably/http/paginatedresult.py index a9b14ae7..b275be7c 100644 --- a/ably/http/paginatedresult.py +++ b/ably/http/paginatedresult.py @@ -13,7 +13,7 @@ def format_time_param(t): try: return '%d' % (calendar.timegm(t.utctimetuple()) * 1000) - except: + except Exception: return str(t) def format_params(params=None, direction=None, start=None, end=None, limit=None, **kw): diff --git a/ably/rest/push.py b/ably/rest/push.py index 752a699d..cdb043c4 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -74,8 +74,8 @@ def get(self, device_id): """ path = '/push/deviceRegistrations/%s' % device_id response = self.ably.http.get(path) - details = response.to_native() - return DeviceDetails(**details) + obj = response.to_native() + return DeviceDetails.from_dict(obj) def list(self, **params): """Returns a PaginatedResult object with the list of DeviceDetails @@ -96,11 +96,12 @@ def save(self, device): :Parameters: - `device`: a dictionary with the device information """ - device_details = DeviceDetails(**device) + device_details = DeviceDetails.factory(device) path = '/push/deviceRegistrations/%s' % device_details.id - response = self.ably.http.put(path, body=device) - details = response.to_native() - return DeviceDetails(**details) + body = device_details.as_dict() + response = self.ably.http.put(path, body=body) + obj = response.to_native() + return DeviceDetails.from_dict(obj) def remove(self, device_id): """Deletes the registered device identified by the given device id. diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index a18bc359..8cc9ca15 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -37,13 +37,13 @@ def as_dict(self): return obj @classmethod - def from_dict(self, obj): + def from_dict(cls, obj): obj = {camel_to_snake(key): value for key, value in obj.items()} - return self(**obj) + return cls(**obj) @classmethod - def factory(self, subscription): - if isinstance(subscription, self): + def factory(cls, subscription): + if isinstance(subscription, cls): return subscription - return self.from_dict(subscription) + return cls.from_dict(subscription) diff --git a/ably/types/device.py b/ably/types/device.py index 60bda43c..9f482068 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -1,3 +1,6 @@ +from .utils import camel_to_snake, snake_to_camel + + DevicePushTransportType = {'fcm', 'gcm', 'apns', 'web'} DevicePlatform = {'android', 'ios', 'browser'} DeviceFormFactor = {'phone', 'tablet', 'desktop', 'tv', 'watch', 'car', 'embedded', 'other'} @@ -5,9 +8,9 @@ class DeviceDetails(object): - def __init__(self, id, clientId=None, formFactor=None, metadata=None, - platform=None, push=None, updateToken=None, appId=None, - deviceIdentityToken=None): + def __init__(self, id, client_id=None, form_factor=None, metadata=None, + platform=None, push=None, update_token=None, app_id=None, + device_identity_token=None): if push: recipient = push.get('recipient') @@ -19,18 +22,18 @@ def __init__(self, id, clientId=None, formFactor=None, metadata=None, if platform is not None and platform not in DevicePlatform: raise ValueError('unexpected platform {}'.format(platform)) - if formFactor is not None and formFactor not in DeviceFormFactor: - raise ValueError('unexpected form factor {}'.format(formFactor)) + if form_factor is not None and form_factor not in DeviceFormFactor: + raise ValueError('unexpected form factor {}'.format(form_factor)) self.__id = id - self.__client_id = clientId - self.__form_factor = formFactor + self.__client_id = client_id + self.__form_factor = form_factor self.__metadata = metadata self.__platform = platform self.__push = push - self.__update_token = updateToken - self.__app_id = appId - self.__device_identity_token = deviceIdentityToken + self.__update_token = update_token + self.__app_id = app_id + self.__device_identity_token = device_identity_token @property def id(self): @@ -68,13 +71,34 @@ def app_id(self): def device_identity_token(self): return self.__device_identity_token + def as_dict(self): + keys = ['id', 'client_id', 'form_factor', 'metadata', 'platform', + 'push', 'update_token', 'app_id', 'device_identity_token'] + + obj = {} + for key in keys: + value = getattr(self, key) + if value is not None: + key = snake_to_camel(key) + obj[key] = value + + return obj + + @classmethod + def from_dict(cls, obj): + obj = {camel_to_snake(key): value for key, value in obj.items()} + return cls(**obj) + @classmethod def from_array(cls, array): return [cls.from_dict(d) for d in array] @classmethod - def from_dict(cls, data): - return cls(**data) + def factory(cls, device): + if isinstance(device, cls): + return device + + return cls.from_dict(device) def make_device_details_response_processor(binary): diff --git a/ably/types/mixins.py b/ably/types/mixins.py index 4c360e70..31dbd478 100644 --- a/ably/types/mixins.py +++ b/ably/types/mixins.py @@ -1,8 +1,7 @@ -import six -import json import base64 - +import json import logging +import six from ably.util.crypto import CipherData diff --git a/requirements-test.txt b/requirements-test.txt index 5fdd4aba..28d0d4c5 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,12 +3,11 @@ pycryptodome requests>=2.7.0,<3 six>=1.9.0 -flake8>=3.2.1,<4 -flake8-import-order>=0.11 mock>=1.3.0,<2.0 pep8-naming>=0.4.1 pytest>=3.0.5 pytest-cov>=2.4.0,<3 +pytest-flake8 #pytest-mock>=1.5.0,<2 #pytest-timeout>=1.2.0,<2 pytest-xdist>=1.15.0,<2 diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 23d2f1a3..37c2583e 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -69,7 +69,7 @@ def token_callback(token_params): try: ably.stats(None) - except: + except Exception: pass self.assertTrue(callback_called, msg="Token callback not called") diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 1eaa1bf2..d8d1e299 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -23,35 +23,35 @@ class TestPush(BaseTestCase): @classmethod - def setUpClass(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + def setUpClass(cls): + cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], + rest_host=test_vars["host"], + port=test_vars["port"], + tls_port=test_vars["tls_port"], + tls=test_vars["tls"]) # Register several devices for later use - self.devices = {} + cls.devices = {} for i in range(10): - self.save_device() + cls.save_device() def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @classmethod - def get_client_id(self): + def get_client_id(cls): return random_string(12) @classmethod - def get_device_id(self): + def get_device_id(cls): return random_string(26, string.ascii_uppercase + string.digits) @classmethod - def gen_device_data(self, data=None, **kw): + def gen_device_data(cls, data=None, **kw): if data is None: data = { - 'id': self.get_device_id(), - 'clientId': self.get_client_id(), + 'id': cls.get_device_id(), + 'clientId': cls.get_client_id(), 'platform': random.choice(['android', 'ios']), 'formFactor': 'phone', 'push': { @@ -68,34 +68,34 @@ def gen_device_data(self, data=None, **kw): return data @classmethod - def save_device(self, data=None, **kw): + def save_device(cls, data=None, **kw): """ Helper method to register a device, to not have this code repeated everywhere. Returns the input dict that was sent to Ably, and the device details returned by Ably. """ - data = self.gen_device_data(data, **kw) - device = self.ably.push.admin.device_registrations.save(data) - self.devices[device.id] = device + data = cls.gen_device_data(data, **kw) + device = cls.ably.push.admin.device_registrations.save(data) + cls.devices[device.id] = device return device @classmethod - def remove_device(self, device_id): - result = self.ably.push.admin.device_registrations.remove(device_id) - self.devices.pop(device_id, None) + def remove_device(cls, device_id): + result = cls.ably.push.admin.device_registrations.remove(device_id) + cls.devices.pop(device_id, None) return result @classmethod - def remove_device_where(self, **kw): - remove_where = self.ably.push.admin.device_registrations.remove_where + def remove_device_where(cls, **kw): + remove_where = cls.ably.push.admin.device_registrations.remove_where result = remove_where(**kw) aux = {'deviceId': 'id', 'clientId': 'client_id'} - for device in list(self.devices.values()): + for device in list(cls.devices.values()): for key, value in kw.items(): key = aux[key] if getattr(device, key) == value: - del self.devices[device.id] + del cls.devices[device.id] return result @@ -111,10 +111,14 @@ def test_admin_publish(self): } publish = self.ably.push.admin.publish - with pytest.raises(TypeError): publish('ablyChannel', data) - with pytest.raises(TypeError): publish(recipient, 25) - with pytest.raises(ValueError): publish({}, data) - with pytest.raises(ValueError): publish(recipient, {}) + with pytest.raises(TypeError): + publish('ablyChannel', data) + with pytest.raises(TypeError): + publish(recipient, 25) + with pytest.raises(ValueError): + publish({}, data) + with pytest.raises(ValueError): + publish(recipient, {}) response = publish(recipient, data) assert response.status_code == 204 @@ -186,9 +190,10 @@ def test_admin_device_registrations_remove(self): device = self.get_device() # Remove - assert get(device.id).id == device.id # Exists + assert get(device.id).id == device.id # Exists assert self.remove_device(device.id).status_code == 204 - with pytest.raises(AblyException): get(device.id) # Doesn't exist + with pytest.raises(AblyException): # Doesn't exist + get(device.id) # Remove again, it doesn't fail assert self.remove_device(device.id).status_code == 204 @@ -199,13 +204,14 @@ def test_admin_device_registrations_remove_where(self): # Remove by device id device = self.get_device() - assert get(device.id).id == device.id # Exists + assert get(device.id).id == device.id # Exists assert self.remove_device_where(deviceId=device.id).status_code == 204 - with pytest.raises(AblyException): get(device.id) # Doesn't exist + with pytest.raises(AblyException): # Doesn't exist + get(device.id) # Remove by client id device = self.get_device() - assert get(device.id).id == device.id # Exists + assert get(device.id).id == device.id # Exists assert self.remove_device_where(clientId=device.client_id).status_code == 204 # Doesn't exist (Deletion is async: wait up to a few seconds before giving up) with pytest.raises(AblyException): @@ -247,7 +253,9 @@ def test_admin_channel_subscriptions_save(self): PushChannelSubscription(channel, device_id=device.id, client_id=client_id) subscription = PushChannelSubscription('notallowed', device_id=device.id) - with pytest.raises(AblyAuthException): save(subscription) + with pytest.raises(AblyAuthException): + save(subscription) subscription = PushChannelSubscription(channel, device_id='notregistered') - with pytest.raises(AblyException): save(subscription) + with pytest.raises(AblyException): + save(subscription) diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index de557d13..e7fe5a20 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -44,7 +44,6 @@ def test_post(self): assert result.items[0]['channel'] == self.channel assert 'messageId' in result.items[0] - def test_get(self): params = {'limit': 10, 'direction': 'forwards'} result = self.ably.request('GET', self.path, params=params) From 5a087576482e4965f1f8025d2af47f5eff531f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 17:14:50 +0200 Subject: [PATCH 19/85] Fixing test requirements to run pytest-flake8 --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 28d0d4c5..050c8a5a 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -5,7 +5,7 @@ six>=1.9.0 mock>=1.3.0,<2.0 pep8-naming>=0.4.1 -pytest>=3.0.5 +pytest>=3.5 pytest-cov>=2.4.0,<3 pytest-flake8 #pytest-mock>=1.5.0,<2 From 8f08f1957b990e6c572ea87ebb512135a061b7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 18:21:51 +0200 Subject: [PATCH 20/85] Trying to fix random test failures that only happen in travis --- .travis.yml | 2 +- test/ably/restchannelpublish_test.py | 9 ++++----- test/ably/restcrypto_test.py | 22 +++++++++------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index bef329d6..f946ba50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ sudo: false install: - travis_retry pip install -r requirements-test.txt script: - - py.test --tb=short --flake8 + - py.test --flake8 after_success: - "if [ $TRAVIS_PYTHON_VERSION == '3.6' ]; then pip install coveralls; coveralls; fi" diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 59a39bbc..6347c115 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -150,11 +150,10 @@ def test_publish_message_null_name(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") - - self.assertIsNone(messages[0].name) - self.assertEqual(messages[0].data, data) + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" + assert messages[0].name is None + assert messages[0].data == data def test_publish_message_null_data(self): channel = self.ably.channels[ diff --git a/test/ably/restcrypto_test.py b/test/ably/restcrypto_test.py index 7b8fb5b2..391d446b 100644 --- a/test/ably/restcrypto_test.py +++ b/test/ably/restcrypto_test.py @@ -5,6 +5,7 @@ import logging import base64 +import pytest import six from ably import AblyException @@ -145,20 +146,15 @@ def test_crypto_publish_key_mismatch(self): rx_channel = self.ably2.channels.get(channel_name, cipher={'key': generate_random_key()}) - try: - with self.assertRaises(AblyException) as cm: - messages = rx_channel.history() - except Exception as e: - log.debug('test_crypto_publish_key_mismatch_fail: rx_channel.history not creating exception') - log.debug(messages.items[0].data) + with pytest.raises(AblyException) as excinfo: + rx_channel.history() - raise(e) - - the_exception = cm.exception - self.assertTrue( - 'invalid-padding' == the_exception.message or - the_exception.message.startswith("UnicodeDecodeError: 'utf8'") or - the_exception.message.startswith("UnicodeDecodeError: 'utf-8'")) + message = excinfo.value.message + assert ( + 'invalid-padding' == message or + message.startswith("UnicodeDecodeError: 'utf8'") or + message.startswith("UnicodeDecodeError: 'utf-8'") + ) def test_crypto_send_unencrypted(self): channel_name = self.get_channel_name('persisted:crypto_send_unencrypted') From fdf5cd2b3acdc7efe05a2e30773b037a3aff19e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 19:01:02 +0200 Subject: [PATCH 21/85] pytest idioms: use assert instead of self.assertXXX We moved from unittest to pytest a while back, but we're still using unittest idioms. While they work we should use pytest idioms instead. This may help debugging/fixing the random errors we see in Travis. --- test/ably/restchannelpublish_test.py | 179 +++++++++++++-------------- 1 file changed, 86 insertions(+), 93 deletions(-) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 6347c115..d71a2090 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -6,11 +6,12 @@ import os import uuid -import six -from six.moves import range import mock import msgpack +import pytest import requests +import six +from six.moves import range from ably import AblyException, IncompatibleClientIdException from ably import AblyRest @@ -58,31 +59,30 @@ def test_publish_various_datatypes_text(self): # Get the history for this channel history = publish0.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(4, len(messages), msg="Expected 4 messages") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 4, "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) log.debug("message_contents: %s" % str(message_contents)) - self.assertEqual(six.u("This is a string message payload"), - message_contents["publish0"], - msg="Expect publish0 to be expected String)") - self.assertEqual(b"This is a byte[] message payload", - message_contents["publish1"], - msg="Expect publish1 to be expected byte[]. Actual: %s" % - str(message_contents['publish1'])) - self.assertEqual({"test": "This is a JSONObject message payload"}, - message_contents["publish2"], - msg="Expect publish2 to be expected JSONObject") - self.assertEqual(["This is a JSONArray message payload"], - message_contents["publish3"], - msg="Expect publish3 to be expected JSONObject") + assert message_contents["publish0"] == six.u("This is a string message payload"), \ + "Expect publish0 to be expected String)" + + assert message_contents["publish1"] == b"This is a byte[] message payload", \ + "Expect publish1 to be expected byte[]. Actual: %s" % str(message_contents['publish1']) + + assert message_contents["publish2"] == {"test": "This is a JSONObject message payload"}, \ + "Expect publish2 to be expected JSONObject" + + assert message_contents["publish3"] == ["This is a JSONArray message payload"], \ + "Expect publish3 to be expected JSONObject" @dont_vary_protocol def test_unsuporsed_payload_must_raise_exception(self): channel = self.ably.channels["persisted:publish0"] for data in [1, 1.1, True]: - self.assertRaises(AblyException, channel.publish, 'event', data) + with pytest.raises(AblyException): + channel.publish('event', data) def test_publish_message_list(self): channel = self.ably.channels[ @@ -96,12 +96,12 @@ def test_publish_message_list(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), len(expected_messages), msg="Expected 3 messages") + assert messages is not None, "Expected non-None messages" + assert len(messages) == len(expected_messages), "Expected 3 messages" for m, expected_m in zip(messages, reversed(expected_messages)): - self.assertEqual(m.name, expected_m.name) - self.assertEqual(m.data, expected_m.data) + assert m.name == expected_m.name + assert m.data == expected_m.data def test_message_list_generate_one_request(self): channel = self.ably.channels[ @@ -112,7 +112,7 @@ def test_message_list_generate_one_request(self): with mock.patch('ably.rest.rest.Http.post', wraps=channel.ably.http.post) as post_mock: channel.publish(messages=expected_messages) - self.assertEqual(post_mock.call_count, 1) + assert post_mock.call_count == 1 if self.use_binary_protocol: messages = msgpack.unpackb(post_mock.call_args[1]['body'], encoding='utf-8') @@ -120,8 +120,8 @@ def test_message_list_generate_one_request(self): messages = json.loads(post_mock.call_args[1]['body']) for i, message in enumerate(messages): - self.assertEqual(message['name'], 'name-' + str(i)) - self.assertEqual(message['data'], six.text_type(i)) + assert message['name'] == 'name-' + str(i) + assert message['data'] == six.text_type(i) def test_publish_error(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"], @@ -133,11 +133,11 @@ def test_publish_error(self): ably.auth.authorize( token_params={'capability': {"only_subscribe": ["subscribe"]}}) - with self.assertRaises(AblyException) as cm: + with pytest.raises(AblyException) as excinfo: ably.channels["only_subscribe"].publish() - self.assertEqual(401, cm.exception.status_code) - self.assertEqual(40160, cm.exception.code) + assert 401 == excinfo.value.status_code + assert 40160 == excinfo.value.code def test_publish_message_null_name(self): channel = self.ably.channels[ @@ -166,11 +166,11 @@ def test_publish_message_null_data(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" - self.assertEqual(messages[0].name, name) - self.assertIsNone(messages[0].data) + assert messages[0].name == name + assert messages[0].data is None def test_publish_message_null_name_and_data(self): channel = self.ably.channels[ @@ -183,12 +183,12 @@ def test_publish_message_null_name_and_data(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 2, msg="Expected 2 messages") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 2, "Expected 2 messages" for m in messages: - self.assertIsNone(m.name) - self.assertIsNone(m.data) + assert m.name is None + assert m.data is None def test_publish_message_null_name_and_data_keys_arent_sent(self): channel = self.ably.channels[ @@ -201,19 +201,19 @@ def test_publish_message_null_name_and_data_keys_arent_sent(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" - self.assertEqual(post_mock.call_count, 1) + assert post_mock.call_count == 1 if self.use_binary_protocol: posted_body = msgpack.unpackb(post_mock.call_args[1]['body'], encoding='utf-8') else: posted_body = json.loads(post_mock.call_args[1]['body']) - self.assertIn('timestamp', posted_body) - self.assertNotIn('name', posted_body) - self.assertNotIn('data', posted_body) + assert 'timestamp' in posted_body + assert 'name' not in posted_body + assert 'data' not in posted_body def test_message_attr(self): publish0 = self.ably.channels[ @@ -227,18 +227,17 @@ def test_message_attr(self): # Get the history for this channel history = publish0.history() message = history.items[0] - self.assertIsInstance(message, Message) - self.assertTrue(message.id) - self.assertTrue(message.name) - self.assertEqual(message.data, - {six.u('test'): six.u('This is a JSONObject message payload')}) - self.assertEqual(message.encoding, '') - self.assertEqual(message.client_id, 'client_id') - self.assertIsInstance(message.timestamp, int) + assert isinstance(message, Message) + assert message.id + assert message.name + assert message.data == {six.u('test'): six.u('This is a JSONObject message payload')} + assert message.encoding == '' + assert message.client_id == 'client_id' + assert isinstance(message.timestamp, int) def test_token_is_bound_to_options_client_id_after_publish(self): # null before publish - self.assertIsNone(self.ably_with_client_id.auth.token_details) + assert self.ably_with_client_id.auth.token_details is None # created after message publish and will have client_id channel = self.ably_with_client_id.channels[ @@ -246,10 +245,10 @@ def test_token_is_bound_to_options_client_id_after_publish(self): channel.publish(name='publish', data='test') # defined after publish - self.assertIsInstance(self.ably_with_client_id.auth.token_details, TokenDetails) - self.assertEqual(self.ably_with_client_id.auth.token_details.client_id, self.client_id) - self.assertEqual(self.ably_with_client_id.auth.auth_mechanism, Auth.Method.TOKEN) - self.assertEqual(channel.history().items[0].client_id, self.client_id) + assert isinstance(self.ably_with_client_id.auth.token_details, TokenDetails) + assert self.ably_with_client_id.auth.token_details.client_id == self.client_id + assert self.ably_with_client_id.auth.auth_mechanism == Auth.Method.TOKEN + assert channel.history().items[0].client_id == self.client_id def test_publish_message_without_client_id_on_identified_client(self): channel = self.ably_with_client_id.channels[ @@ -262,10 +261,10 @@ def test_publish_message_without_client_id_on_identified_client(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" - self.assertEqual(post_mock.call_count, 2) + assert post_mock.call_count == 2 if self.use_binary_protocol: posted_body = msgpack.unpackb( @@ -274,16 +273,16 @@ def test_publish_message_without_client_id_on_identified_client(self): posted_body = json.loads( post_mock.mock_calls[0][2]['body']) - self.assertNotIn('client_id', posted_body) + assert 'client_id' not in posted_body # Get the history for this channel history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" - self.assertEqual(messages[0].client_id, self.ably_with_client_id.client_id) + assert messages[0].client_id == self.ably_with_client_id.client_id def test_publish_message_with_client_id_on_identified_client(self): # works if same @@ -295,15 +294,14 @@ def test_publish_message_with_client_id_on_identified_client(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 1, msg="Expected 1 message") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 1, "Expected 1 message" - self.assertEqual(messages[0].client_id, self.ably_with_client_id.client_id) + assert messages[0].client_id == self.ably_with_client_id.client_id # fails if different - with self.assertRaises(IncompatibleClientIdException): - channel.publish(name='publish', data='test', - client_id='invalid') + with pytest.raises(IncompatibleClientIdException): + channel.publish(name='publish', data='test', client_id='invalid') def test_publish_message_with_wrong_client_id_on_implicit_identified_client(self): new_token = self.ably.auth.authorize( @@ -317,13 +315,11 @@ def test_publish_message_with_wrong_client_id_on_implicit_identified_client(self channel = new_ably.channels[ self.get_channel_name('persisted:wrong_client_id_implicit_client')] - with self.assertRaises(AblyException) as cm: - channel.publish(name='publish', data='test', - client_id='invalid') + with pytest.raises(AblyException) as excinfo: + channel.publish(name='publish', data='test', client_id='invalid') - the_exception = cm.exception - self.assertEqual(400, the_exception.status_code) - self.assertEqual(40012, the_exception.code) + assert 400 == excinfo.value.status_code + assert 40012 == excinfo.value.code # RSA15b def test_wildcard_client_id_can_publish_as_others(self): @@ -335,7 +331,7 @@ def test_wildcard_client_id_can_publish_as_others(self): tls=test_vars["tls"], use_binary_protocol=self.use_binary_protocol) - self.assertEqual(wildcard_ably.auth.client_id, '*') + assert wildcard_ably.auth.client_id == '*' channel = wildcard_ably.channels[ self.get_channel_name('persisted:wildcard_client_id')] channel.publish(name='publish1', data='no client_id') @@ -346,22 +342,22 @@ def test_wildcard_client_id_can_publish_as_others(self): history = channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(len(messages), 2, msg="Expected 2 messages") + assert messages is not None, "Expected non-None messages" + assert len(messages) == 2, "Expected 2 messages" - self.assertEqual(messages[0].client_id, some_client_id) - self.assertIsNone(messages[1].client_id) + assert messages[0].client_id == some_client_id + assert messages[1].client_id is None # TM2h @dont_vary_protocol def test_invalid_connection_key(self): channel = self.ably.channels["persisted:invalid_connection_key"] message = Message(data='payload', connection_key='should.be.wrong') - with self.assertRaises(AblyException) as cm: + with pytest.raises(AblyException) as excinfo: channel.publish(messages=[message]) - self.assertEqual(400, cm.exception.status_code) - self.assertEqual(40006, cm.exception.code) + assert 400 == excinfo.value.status_code + assert 40006 == excinfo.value.code # TM2i, RSL6a2, RSL1h def test_publish_extras(self): @@ -377,9 +373,9 @@ def test_publish_extras(self): # Get the history for this channel history = channel.history() message = history.items[0] - self.assertEqual(message.name, 'test-name') - self.assertEqual(message.data, 'test-data') - self.assertEqual(message.extras, extras) + assert message.name == 'test-name' + assert message.data == 'test-data' + assert message.extras == extras # RSL6a1 def test_interoperability(self): @@ -416,18 +412,15 @@ def test_interoperability(self): channel.publish(data=expected_value) r = requests.get(url, auth=auth) item = r.json()[0] - self.assertEqual(item.get('encoding'), encoding) + assert item.get('encoding') == encoding if encoding == 'json': - self.assertEqual( - json.loads(item['data']), - json.loads(data), - ) + assert json.loads(item['data']) == json.loads(data) else: - self.assertEqual(item['data'], data) + assert item['data'] == data # 2) channel.publish(messages=[Message(data=data, encoding=encoding)]) history = channel.history() message = history.items[0] - self.assertEqual(message.data, expected_value) - self.assertEqual(type(message.data), type_mapping[expected_type]) + assert message.data == expected_value + assert type(message.data) == type_mapping[expected_type] From 1f0321edf3dc61d2c841cbc5c2410bc9ab12ca0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 27 Jun 2018 19:26:17 +0200 Subject: [PATCH 22/85] tests: use assert in restchannelhistory_test --- test/ably/restchannelhistory_test.py | 149 +++++++++------------------ 1 file changed, 51 insertions(+), 98 deletions(-) diff --git a/test/ably/restchannelhistory_test.py b/test/ably/restchannelhistory_test.py index cdf796c0..6837d618 100644 --- a/test/ably/restchannelhistory_test.py +++ b/test/ably/restchannelhistory_test.py @@ -3,6 +3,7 @@ import logging import time +import pytest import responses import six from six.moves import range @@ -43,24 +44,20 @@ def test_channel_history_types(self): history0.publish('history3', ['This is a JSONArray message payload']) history = history0.history() - self.assertIsInstance(history, PaginatedResult) + assert isinstance(history, PaginatedResult) messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(4, len(messages), msg="Expected 4 messages") + assert messages is not None, "Expected non-None messages" + assert 4 == len(messages), "Expected 4 messages" message_contents = {m.name: m for m in messages} - self.assertEqual(six.u("This is a string message payload"), - message_contents["history0"].data, - msg="Expect history0 to be expected String)") - self.assertEqual(b"This is a byte[] message payload", - message_contents["history1"].data, - msg="Expect history1 to be expected byte[]") - self.assertEqual({"test": "This is a JSONObject message payload"}, - message_contents["history2"].data, - msg="Expect history2 to be expected JSONObject") - self.assertEqual(["This is a JSONArray message payload"], - message_contents["history3"].data, - msg="Expect history3 to be expected JSONObject") + assert six.u("This is a string message payload") == message_contents["history0"].data, \ + "Expect history0 to be expected String)" + assert b"This is a byte[] message payload" == message_contents["history1"].data, \ + "Expect history1 to be expected byte[]" + assert {"test": "This is a JSONObject message payload"} == message_contents["history2"].data, \ + "Expect history2 to be expected JSONObject" + assert ["This is a JSONArray message payload"] == message_contents["history3"].data, \ + "Expect history3 to be expected JSONObject" expected_message_history = [ message_contents['history3'], @@ -68,9 +65,7 @@ def test_channel_history_types(self): message_contents['history1'], message_contents['history0'], ] - - self.assertEqual(expected_message_history, messages, - msg="Expect messages in reverse order") + assert expected_message_history == messages, "Expect messages in reverse order" def test_channel_history_multi_50_forwards(self): history0 = self.ably.channels[ @@ -80,7 +75,7 @@ def test_channel_history_multi_50_forwards(self): history0.publish('history%d' % i, str(i)) history = history0.history(direction='forwards') - self.assertIsNotNone(history) + assert history is not None messages = history.items assert len(messages) == 50, "Expected 50 messages" @@ -96,16 +91,13 @@ def test_channel_history_multi_50_backwards(self): history0.publish('history%d' % i, str(i)) history = history0.history(direction='backwards') - self.assertIsNotNone(history) + assert history is not None messages = history.items - self.assertEqual(50, len(messages), - msg="Expected 50 messages") + assert 50 == len(messages), "Expected 50 messages" message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(49, -1, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expect messages in reverse order') + assert expected_messages == messages, 'Expect messages in reverse order' def history_mock_url(self, channel_name): kwargs = { @@ -129,7 +121,7 @@ def test_channel_history_default_limit(self): url = self.history_mock_url('persisted:channelhistory_limit') self.responses_add_empty_msg_pack(url) channel.history() - self.assertNotIn('limit=', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=' not in responses.calls[0].request.url.split('?')[-1] @responses.activate @dont_vary_protocol @@ -139,14 +131,14 @@ def test_channel_history_with_limits(self): url = self.history_mock_url('persisted:channelhistory_limit') self.responses_add_empty_msg_pack(url) channel.history(limit=500) - self.assertIn('limit=500', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=500' in responses.calls[0].request.url.split('?')[-1] channel.history(limit=1000) - self.assertIn('limit=1000', responses.calls[1].request.url.split('?')[-1]) + assert 'limit=1000' in responses.calls[1].request.url.split('?')[-1] @dont_vary_protocol def test_channel_history_max_limit_is_1000(self): channel = self.ably.channels['persisted:channelhistory_limit'] - with self.assertRaises(AblyException): + with pytest.raises(AblyException): channel.history(limit=1001) def test_channel_history_limit_forwards(self): @@ -157,7 +149,7 @@ def test_channel_history_limit_forwards(self): history0.publish('history%d' % i, str(i)) history = history0.history(direction='forwards', limit=25) - self.assertIsNotNone(history) + assert history is not None messages = history.items assert len(messages) == 25, "Expected 25 messages" @@ -173,7 +165,7 @@ def test_channel_history_limit_backwards(self): history0.publish('history%d' % i, str(i)) history = history0.history(direction='backwards', limit=25) - self.assertIsNotNone(history) + assert history is not None messages = history.items assert len(messages) == 25, "Expected 25 messages" @@ -202,13 +194,11 @@ def test_channel_history_time_forwards(self): end=interval_end) messages = history.items - self.assertEqual(20, len(messages)) + assert 20 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(20, 40)] - - self.assertEqual(expected_messages, messages, - msg='Expect messages in forward order') + assert expected_messages == messages, 'Expect messages in forward order' def test_channel_history_time_backwards(self): history0 = self.ably.channels[ @@ -231,13 +221,11 @@ def test_channel_history_time_backwards(self): end=interval_end) messages = history.items - self.assertEqual(20, len(messages)) + assert 20 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(39, 19, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expect messages in reverse order') + assert expected_messages, messages == 'Expect messages in reverse order' def test_channel_history_paginate_forwards(self): history0 = self.ably.channels[ @@ -249,35 +237,27 @@ def test_channel_history_paginate_forwards(self): history = history0.history(direction='forwards', limit=10) messages = history.items - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(10, 20)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(20, 30)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_backwards(self): history0 = self.ably.channels[ @@ -288,36 +268,27 @@ def test_channel_history_paginate_backwards(self): history = history0.history(direction='backwards', limit=10) messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(39, 29, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(29, 19, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_forwards_first(self): history0 = self.ably.channels[ @@ -328,36 +299,27 @@ def test_channel_history_paginate_forwards_first(self): history = history0.history(direction='forwards', limit=10) messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(10, 20)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.first() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(0, 10)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_backwards_rel_first(self): history0 = self.ably.channels[ @@ -368,33 +330,24 @@ def test_channel_history_paginate_backwards_rel_first(self): history = history0.history(direction='backwards', limit=10) messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.next() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(39, 29, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' history = history.first() messages = history.items - - self.assertEqual(10, len(messages)) + assert 10 == len(messages) message_contents = {m.name:m for m in messages} expected_messages = [message_contents['history%d' % i] for i in range(49, 39, -1)] - - self.assertEqual(expected_messages, messages, - msg='Expected 10 messages') + assert expected_messages == messages, 'Expected 10 messages' From 3ac77ac4af002cd74a2f7b7332e96c40cc1a6a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 28 Jun 2018 17:21:21 +0200 Subject: [PATCH 23/85] tests: use assert in restauth_test --- test/ably/restauth_test.py | 208 ++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 116 deletions(-) diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 37c2583e..5330f908 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -9,6 +9,7 @@ import warnings import mock +import pytest from requests import Session import six from six.moves.urllib.parse import parse_qs, urlparse @@ -33,25 +34,21 @@ class TestAuth(BaseTestCase): def test_auth_init_key_only(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"]) - self.assertEqual(Auth.Method.BASIC, ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") - self.assertEqual(ably.auth.auth_options.key_name, - test_vars["keys"][0]['key_name']) - self.assertEqual(ably.auth.auth_options.key_secret, - test_vars["keys"][0]['key_secret']) + assert Auth.Method.BASIC == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" + assert ably.auth.auth_options.key_name == test_vars["keys"][0]['key_name'] + assert ably.auth.auth_options.key_secret == test_vars["keys"][0]['key_secret'] def test_auth_init_token_only(self): ably = AblyRest(token="this_is_not_really_a_token") - self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") + assert Auth.Method.TOKEN == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" def test_auth_token_details(self): td = TokenDetails() ably = AblyRest(token_details=td) - self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism) - self.assertIs(ably.auth.token_details, td) + assert Auth.Method.TOKEN == ably.auth.auth_mechanism + assert ably.auth.token_details is td def test_auth_init_with_token_callback(self): callback_called = [] @@ -72,16 +69,14 @@ def token_callback(token_params): except Exception: pass - self.assertTrue(callback_called, msg="Token callback not called") - self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") + assert callback_called, "Token callback not called" + assert Auth.Method.TOKEN == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" def test_auth_init_with_key_and_client_id(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"], client_id='testClientId') - self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") - self.assertEqual(ably.auth.client_id, 'testClientId') + assert Auth.Method.TOKEN == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" + assert ably.auth.client_id == 'testClientId' def test_auth_init_with_token(self): @@ -91,8 +86,7 @@ def test_auth_init_with_token(self): tls_port=test_vars["tls_port"], tls=test_vars["tls"]) - self.assertEqual(Auth.Method.TOKEN, ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") + assert Auth.Method.TOKEN == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" # RSA11 def test_request_basic_auth_header(self): @@ -105,10 +99,7 @@ def test_request_basic_auth_header(self): pass request = get_mock.call_args_list[0][0][0] authorization = request.headers['Authorization'] - self.assertEqual(authorization, - 'Basic %s' % - base64.b64encode('bar:foo'.encode('ascii') - ).decode('utf-8')) + assert authorization == 'Basic %s' % base64.b64encode('bar:foo'.encode('ascii')).decode('utf-8') def test_request_token_auth_header(self): ably = AblyRest(token='not_a_real_token') @@ -120,55 +111,52 @@ def test_request_token_auth_header(self): pass request = get_mock.call_args_list[0][0][0] authorization = request.headers['Authorization'] - self.assertEqual(authorization, - 'Bearer %s' % - base64.b64encode('not_a_real_token'.encode('ascii') - ).decode('utf-8')) + assert authorization == 'Bearer %s' % base64.b64encode('not_a_real_token'.encode('ascii')).decode('utf-8') def test_if_cant_authenticate_via_token(self): - self.assertRaises(ValueError, AblyRest, use_token_auth=True) + with pytest.raises(ValueError): + AblyRest(use_token_auth=True) def test_use_auth_token(self): ably = AblyRest(use_token_auth=True, key=test_vars["keys"][0]["key_str"]) - self.assertEquals(ably.auth.auth_mechanism, Auth.Method.TOKEN) + assert ably.auth.auth_mechanism == Auth.Method.TOKEN def test_with_client_id(self): ably = AblyRest(client_id='client_id', key=test_vars["keys"][0]["key_str"]) - self.assertEquals(ably.auth.auth_mechanism, Auth.Method.TOKEN) + assert ably.auth.auth_mechanism == Auth.Method.TOKEN def test_with_auth_url(self): ably = AblyRest(auth_url='auth_url') - self.assertEquals(ably.auth.auth_mechanism, Auth.Method.TOKEN) + assert ably.auth.auth_mechanism == Auth.Method.TOKEN def test_with_auth_callback(self): ably = AblyRest(auth_callback=lambda x: x) - self.assertEquals(ably.auth.auth_mechanism, Auth.Method.TOKEN) + assert ably.auth.auth_mechanism == Auth.Method.TOKEN def test_with_token(self): ably = AblyRest(token='a token') - self.assertEquals(ably.auth.auth_mechanism, Auth.Method.TOKEN) + assert ably.auth.auth_mechanism == Auth.Method.TOKEN def test_default_ttl_is_1hour(self): one_hour_in_ms = 60 * 60 * 1000 - self.assertEquals(TokenDetails.DEFAULTS['ttl'], one_hour_in_ms) + assert TokenDetails.DEFAULTS['ttl'] == one_hour_in_ms def test_with_auth_method(self): ably = AblyRest(token='a token', auth_method='POST') - self.assertEquals(ably.auth.auth_options.auth_method, 'POST') + assert ably.auth.auth_options.auth_method == 'POST' def test_with_auth_headers(self): ably = AblyRest(token='a token', auth_headers={'h1': 'v1'}) - self.assertEquals(ably.auth.auth_options.auth_headers, {'h1': 'v1'}) + assert ably.auth.auth_options.auth_headers == {'h1': 'v1'} def test_with_auth_params(self): ably = AblyRest(token='a token', auth_params={'p': 'v'}) - self.assertEquals(ably.auth.auth_options.auth_params, {'p': 'v'}) + assert ably.auth.auth_options.auth_params == {'p': 'v'} def test_with_default_token_params(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"], default_token_params={'ttl': 12345}) - self.assertEquals(ably.auth.auth_options.default_token_params, - {'ttl': 12345}) + assert ably.auth.auth_options.default_token_params == {'ttl': 12345} @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -187,13 +175,11 @@ def per_protocol_setup(self, use_binary_protocol): def test_if_authorize_changes_auth_mechanism_to_token(self): - self.assertEqual(Auth.Method.BASIC, self.ably.auth.auth_mechanism, - msg="Unexpected Auth method mismatch") + assert Auth.Method.BASIC == self.ably.auth.auth_mechanism, "Unexpected Auth method mismatch" self.ably.auth.authorize() - self.assertEqual(Auth.Method.TOKEN, self.ably.auth.auth_mechanism, - msg="Authorise should change the Auth method") + assert Auth.Method.TOKEN == self.ably.auth.auth_mechanism, "Authorise should change the Auth method" # RSA10a @dont_vary_protocol @@ -202,7 +188,7 @@ def test_authorize_always_creates_new_token(self): self.ably.channels.test.publish('event', 'data') self.ably.auth.authorize({'capability': {'test': ['subscribe']}}) - with self.assertRaises(AblyAuthException): + with pytest.raises(AblyAuthException): self.ably.channels.test.publish('event', 'data') def test_authorize_create_new_token_if_expired(self): @@ -213,13 +199,11 @@ def test_authorize_create_new_token_if_expired(self): return_value=True): new_token = self.ably.auth.authorize() - self.assertIsNot(token, new_token) + assert token is not new_token def test_authorize_returns_a_token_details(self): - token = self.ably.auth.authorize() - - self.assertIsInstance(token, TokenDetails) + assert isinstance(token, TokenDetails) @dont_vary_protocol def test_authorize_adheres_to_request_token(self): @@ -229,12 +213,11 @@ def test_authorize_adheres_to_request_token(self): self.ably.auth.authorize(token_params, auth_params) token_called, auth_called = request_mock.call_args - self.assertEqual(token_called[0], token_params) + assert token_called[0] == token_params # Authorise may call request_token with some default auth_options. for arg, value in six.iteritems(auth_params): - self.assertEqual(auth_called[arg], value, - "%s called with wrong value: %s" % (arg, value)) + assert auth_called[arg] == value, "%s called with wrong value: %s" % (arg, value) def test_with_token_str_https(self): token = self.ably.auth.authorize() @@ -261,7 +244,7 @@ def test_if_default_client_id_is_used(self): client_id='my_client_id', use_binary_protocol=self.use_binary_protocol) token = ably.auth.authorize() - self.assertEqual(token.client_id, 'my_client_id') + assert token.client_id == 'my_client_id' # RSA10j def test_if_parameters_are_stored_and_used_as_defaults(self): @@ -274,8 +257,8 @@ def test_if_parameters_are_stored_and_used_as_defaults(self): self.ably.auth.authorize() token_called, auth_called = request_mock.call_args - self.assertEqual(token_called[0], {'ttl': 555}) - self.assertEqual(auth_called['auth_headers'], {'a_headers': 'a_value'}) + assert token_called[0] == {'ttl': 555} + assert auth_called['auth_headers'] == {'a_headers': 'a_value'} # Different parameters, should completely replace the first ones, not merge auth_options = dict(self.ably.auth.auth_options.auth_options) @@ -286,8 +269,8 @@ def test_if_parameters_are_stored_and_used_as_defaults(self): self.ably.auth.authorize() token_called, auth_called = request_mock.call_args - self.assertEqual(token_called[0], {}) - self.assertEqual(auth_called['auth_headers'], None) + assert token_called[0] == {} + assert auth_called['auth_headers'] == None # RSA10g def test_timestamp_is_not_stored(self): @@ -297,7 +280,7 @@ def test_timestamp_is_not_stored(self): token_1 = self.ably.auth.authorize( {'ttl': 60 * 1000, 'client_id': 'new_id'}, auth_options) - self.assertIsInstance(token_1, TokenDetails) + assert isinstance(token_1, TokenDetails) # call authorize again with timestamp set timestamp = self.ably.time() @@ -308,17 +291,17 @@ def test_timestamp_is_not_stored(self): token_2 = self.ably.auth.authorize( {'ttl': 60 * 1000, 'client_id': 'new_id', 'timestamp': timestamp}, auth_options) - self.assertIsInstance(token_2, TokenDetails) - self.assertNotEqual(token_1, token_2) - self.assertEqual(tr_mock.call_args[1]['timestamp'], timestamp) + assert isinstance(token_2, TokenDetails) + assert token_1 != token_2 + assert tr_mock.call_args[1]['timestamp'] == timestamp # call authorize again with no params with mock.patch('ably.rest.auth.TokenRequest', wraps=ably.types.tokenrequest.TokenRequest) as tr_mock: token_4 = self.ably.auth.authorize() - self.assertIsInstance(token_4, TokenDetails) - self.assertNotEqual(token_2, token_4) - self.assertNotEqual(tr_mock.call_args[1]['timestamp'], timestamp) + assert isinstance(token_4, TokenDetails) + assert token_2 != token_4 + assert tr_mock.call_args[1]['timestamp'] != timestamp def test_client_id_precedence(self): client_id = uuid.uuid4().hex @@ -332,13 +315,13 @@ def test_client_id_precedence(self): client_id=client_id, default_token_params={'client_id': overridden_client_id}) token = ably.auth.authorize() - self.assertEqual(token.client_id, client_id) - self.assertEqual(ably.auth.client_id, client_id) + assert token.client_id == client_id + assert ably.auth.client_id == client_id channel = ably.channels[ self.get_channel_name('test_client_id_precedence')] channel.publish('test', 'data') - self.assertEqual(channel.history().items[0].client_id, client_id) + assert channel.history().items[0].client_id == client_id # RSA10l @dont_vary_protocol @@ -348,11 +331,11 @@ def test_authorise(self): warnings.simplefilter("always") token = self.ably.auth.authorise() - self.assertIsInstance(token, TokenDetails) + assert isinstance(token, TokenDetails) # Verify warning is raised ws = [w for w in ws if issubclass(w.category, DeprecationWarning)] - self.assertEqual(len(ws), 1) + assert len(ws) == 1 @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -370,7 +353,7 @@ def test_with_key(self): use_binary_protocol=self.use_binary_protocol) token_details = self.ably.auth.request_token() - self.assertIsInstance(token_details, TokenDetails) + assert isinstance(token_details, TokenDetails) ably = AblyRest(token_details=token_details, rest_host=test_vars["host"], @@ -382,7 +365,7 @@ def test_with_key(self): ably.channels[channel].publish('event', 'foo') - self.assertEqual(ably.channels[channel].history().items[0].data, 'foo') + assert ably.channels[channel].history().items[0].data == 'foo' @dont_vary_protocol @responses.activate @@ -403,16 +386,14 @@ def test_with_auth_url_headers_and_params_POST(self): token_params=token_params, auth_url=url, auth_headers=headers, auth_method='POST', auth_params=auth_params) - self.assertIsInstance(token_details, TokenDetails) - self.assertEquals(len(responses.calls), 1) + assert isinstance(token_details, TokenDetails) + assert len(responses.calls) == 1 request = responses.calls[0].request - self.assertEquals(request.headers['content-type'], - 'application/x-www-form-urlencoded') - self.assertEquals(headers['foo'], request.headers['foo']) - self.assertEquals(urlparse(request.url).query, '') # No querystring! - self.assertEquals(parse_qs(request.body), # TokenParams has precedence - {'foo': ['token'], 'spam': ['eggs']}) - self.assertEquals('token_string', token_details.token) + assert request.headers['content-type'] == 'application/x-www-form-urlencoded' + assert headers['foo'] == request.headers['foo'] + assert urlparse(request.url).query == '' # No querystring! + assert parse_qs(request.body) == {'foo': ['token'], 'spam': ['eggs']} # TokenParams has precedence + assert 'token_string' == token_details.token @dont_vary_protocol @responses.activate @@ -436,19 +417,18 @@ def test_with_auth_url_headers_and_params_GET(self): token_details = self.ably.auth.request_token( token_params=token_params, auth_url=url, auth_headers=headers, auth_params=auth_params) - self.assertEquals('another_token_string', token_details.token) + assert 'another_token_string' == token_details.token request = responses.calls[0].request - self.assertEquals(request.headers['foo'], 'bar') - self.assertNotIn('this', request.headers) - self.assertEquals(parse_qs(urlparse(request.url).query), - {'foo': ['token'], 'spam': ['eggs']}) - self.assertFalse(request.body) + assert request.headers['foo'] == 'bar' + assert 'this' not in request.headers + assert parse_qs(urlparse(request.url).query) == {'foo': ['token'], 'spam': ['eggs']} + assert not request.body @dont_vary_protocol def test_with_callback(self): called_token_params = {'ttl': '3600000'} def callback(token_params): - self.assertEquals(token_params, called_token_params) + assert token_params == called_token_params return 'token_string' self.ably = AblyRest(auth_callback=callback, @@ -459,16 +439,16 @@ def callback(token_params): token_details = self.ably.auth.request_token( token_params=called_token_params, auth_callback=callback) - self.assertIsInstance(token_details, TokenDetails) - self.assertEquals('token_string', token_details.token) + assert isinstance(token_details, TokenDetails) + assert 'token_string' == token_details.token def callback(token_params): - self.assertEquals(token_params, called_token_params) + assert token_params == called_token_params return TokenDetails(token='another_token_string') token_details = self.ably.auth.request_token( token_params=called_token_params, auth_callback=callback) - self.assertEquals('another_token_string', token_details.token) + assert 'another_token_string' == token_details.token @dont_vary_protocol @responses.activate @@ -485,10 +465,8 @@ def test_when_auth_url_has_query_string(self): body='token_string') self.ably.auth.request_token(auth_url=url, auth_headers=headers, - auth_params={'spam': - 'eggs'}) - self.assertTrue(responses.calls[0].request.url.endswith( - '?with=query&spam=eggs')) + auth_params={'spam': 'eggs'}) + assert responses.calls[0].request.url.endswith('?with=query&spam=eggs') @dont_vary_protocol def test_client_id_null_for_anonymous_auth(self): @@ -501,9 +479,9 @@ def test_client_id_null_for_anonymous_auth(self): tls=test_vars["tls"]) token = ably.auth.authorize() - self.assertIsInstance(token, TokenDetails) - self.assertIsNone(token.client_id) - self.assertIsNone(ably.auth.client_id) + assert isinstance(token, TokenDetails) + assert token.client_id is None + assert ably.auth.client_id is None @dont_vary_protocol def test_client_id_null_until_auth(self): @@ -515,14 +493,14 @@ def test_client_id_null_until_auth(self): tls=test_vars["tls"], default_token_params={'client_id': client_id}) # before auth, client_id is None - self.assertIsNone(token_ably.auth.client_id) + assert token_ably.auth.client_id is None token = token_ably.auth.authorize() + assert isinstance(token, TokenDetails) - self.assertIsInstance(token, TokenDetails) # after auth, client_id is defined - self.assertEquals(token.client_id, client_id) - self.assertEquals(token_ably.auth.client_id, client_id) + assert token.client_id == client_id + assert token_ably.auth.client_id == client_id class TestRenewToken(BaseTestCase): @@ -582,13 +560,13 @@ def tearDown(self): def test_when_renewable(self): self.ably.auth.authorize() self.ably.channels[self.channel].publish('evt', 'msg') - self.assertEquals(1, self.token_requests) - self.assertEquals(1, self.publish_attempts) + assert 1 == self.token_requests + assert 1 == self.publish_attempts # Triggers an authentication 401 failure which should automatically request a new token self.ably.channels[self.channel].publish('evt', 'msg') - self.assertEquals(2, self.token_requests) - self.assertEquals(3, self.publish_attempts) + assert 2 == self.token_requests + assert 3 == self.publish_attempts # RSA4a def test_when_not_renewable(self): @@ -599,15 +577,14 @@ def test_when_not_renewable(self): tls=test_vars["tls"], use_binary_protocol=False) self.ably.channels[self.channel].publish('evt', 'msg') - self.assertEquals(1, self.publish_attempts) + assert 1 == self.publish_attempts publish = self.ably.channels[self.channel].publish - self.assertRaisesRegexp( - AblyAuthException, "The provided token is not renewable and there is" - " no means to generate a new token", publish, - 'evt', 'msg') - self.assertEquals(0, self.token_requests) + with pytest.raises(AblyAuthException, match="The provided token is not renewable and there is no means to generate a new token"): + publish('evt', 'msg') + + assert 0 == self.token_requests # RSA4a def test_when_not_renewable_with_token_details(self): @@ -620,12 +597,11 @@ def test_when_not_renewable_with_token_details(self): tls=test_vars["tls"], use_binary_protocol=False) self.ably.channels[self.channel].publish('evt', 'msg') - self.assertEquals(1, self.publish_attempts) + assert 1 == self.publish_attempts publish = self.ably.channels[self.channel].publish - self.assertRaisesRegexp( - AblyAuthException, "The provided token is not renewable and there is" - " no means to generate a new token", publish, - 'evt', 'msg') - self.assertEquals(0, self.token_requests) + with pytest.raises(AblyAuthException, match="The provided token is not renewable and there is no means to generate a new token"): + publish('evt', 'msg') + + assert 0 == self.token_requests From 550b1d891afe96c0e4144a11a468f5dcc79e3edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 28 Jun 2018 17:26:59 +0200 Subject: [PATCH 24/85] Fix flake8 --- test/ably/restauth_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 5330f908..4b0fd43e 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -270,7 +270,7 @@ def test_if_parameters_are_stored_and_used_as_defaults(self): token_called, auth_called = request_mock.call_args assert token_called[0] == {} - assert auth_called['auth_headers'] == None + assert auth_called['auth_headers'] is None # RSA10g def test_timestamp_is_not_stored(self): @@ -392,7 +392,7 @@ def test_with_auth_url_headers_and_params_POST(self): assert request.headers['content-type'] == 'application/x-www-form-urlencoded' assert headers['foo'] == request.headers['foo'] assert urlparse(request.url).query == '' # No querystring! - assert parse_qs(request.body) == {'foo': ['token'], 'spam': ['eggs']} # TokenParams has precedence + assert parse_qs(request.body) == {'foo': ['token'], 'spam': ['eggs']} # TokenParams has precedence assert 'token_string' == token_details.token @dont_vary_protocol From e8b73bf82064a6aad8477b750007e3bc7477b37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Jul 2018 12:10:27 +0200 Subject: [PATCH 25/85] push tests: minor changes, from comments in PR review --- test/ably/restpush_test.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index d8d1e299..469ddf5c 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -231,17 +231,7 @@ def test_admin_channel_subscriptions_save(self): # Subscribe channel = 'canpublish:test' - subscription = PushChannelSubscription(channel, device_id=device.id) - subscription = save(subscription) - assert type(subscription) is PushChannelSubscription - assert subscription.channel == channel - assert subscription.device_id == device.id - assert subscription.client_id is None - - # Update - channel = 'canpublish:test' - subscription = PushChannelSubscription(channel, device_id=device.id) - subscription = save(subscription) + subscription = save(PushChannelSubscription(channel, device_id=device.id)) assert type(subscription) is PushChannelSubscription assert subscription.channel == channel assert subscription.device_id == device.id From 48d0ff5e10aad4cab7cf665f4bf429df075a05e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 3 Jul 2018 17:18:54 +0200 Subject: [PATCH 26/85] tests: use assert in restpresence_test --- test/ably/restpresence_test.py | 92 ++++++++++++++++------------------ 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/test/ably/restpresence_test.py b/test/ably/restpresence_test.py index 1f9eeba4..2704c97b 100644 --- a/test/ably/restpresence_test.py +++ b/test/ably/restpresence_test.py @@ -4,14 +4,13 @@ from datetime import datetime, timedelta +import pytest import six -import msgpack import responses from ably import AblyRest from ably.http.paginatedresult import PaginatedResult -from ably.types.presence import (PresenceMessage, - make_encrypted_presence_response_handler) +from ably.types.presence import PresenceMessage from test.ably.utils import dont_vary_protocol, VaryByProtocolTestsMetaclass, BaseTestCase from test.ably.restsetup import RestSetup @@ -37,42 +36,39 @@ def per_protocol_setup(self, use_binary_protocol): def test_channel_presence_get(self): presence_page = self.channel.presence.get() - self.assertIsInstance(presence_page, PaginatedResult) - self.assertEqual(len(presence_page.items), 6) + assert isinstance(presence_page, PaginatedResult) + assert len(presence_page.items) == 6 member = presence_page.items[0] - self.assertIsInstance(member, PresenceMessage) - self.assertTrue(member.action) - self.assertTrue(member.id) - self.assertTrue(member.client_id) - self.assertTrue(member.data) - self.assertTrue(member.connection_id) - self.assertTrue(member.timestamp) + assert isinstance(member, PresenceMessage) + assert member.action + assert member.id + assert member.client_id + assert member.data + assert member.connection_id + assert member.timestamp def test_channel_presence_history(self): presence_history = self.channel.presence.history() - self.assertIsInstance(presence_history, PaginatedResult) - self.assertEqual(len(presence_history.items), 6) + assert isinstance(presence_history, PaginatedResult) + assert len(presence_history.items) == 6 member = presence_history.items[0] - self.assertIsInstance(member, PresenceMessage) - self.assertTrue(member.action) - self.assertTrue(member.id) - self.assertTrue(member.client_id) - self.assertTrue(member.data) - self.assertTrue(member.connection_id) - self.assertTrue(member.timestamp) - self.assertTrue(member.encoding) + assert isinstance(member, PresenceMessage) + assert member.action + assert member.id + assert member.client_id + assert member.data + assert member.connection_id + assert member.timestamp + assert member.encoding def test_presence_get_encoded(self): presence_history = self.channel.presence.history() - self.assertEqual(presence_history.items[-1].data, six.u("true")) - self.assertEqual(presence_history.items[-2].data, six.u("24")) - self.assertEqual(presence_history.items[-3].data, - six.u("This is a string clientData payload")) + assert presence_history.items[-1].data == six.u("true") + assert presence_history.items[-2].data == six.u("24") + assert presence_history.items[-3].data == six.u("This is a string clientData payload") # this one doesn't have encoding field - self.assertEqual(presence_history.items[-4].data, - six.u('{ "test": "This is a JSONObject clientData payload"}')) - self.assertEqual(presence_history.items[-5].data, - {"example": {"json": "Object"}}) + assert presence_history.items[-4].data == six.u('{ "test": "This is a JSONObject clientData payload"}') + assert presence_history.items[-5].data == {"example": {"json": "Object"}} def test_presence_history_encrypted(self): key = b'0123456789abcdef' @@ -80,8 +76,7 @@ def test_presence_history_encrypted(self): self.channel = self.ably.channels.get('persisted:presence_fixtures', cipher={'key': key}) presence_history = self.channel.presence.history() - self.assertEqual(presence_history.items[0].data, - {'foo': 'bar'}) + assert presence_history.items[0].data == {'foo': 'bar'} def test_presence_get_encrypted(self): key = b'0123456789abcdef' @@ -93,19 +88,18 @@ def test_presence_get_encrypted(self): lambda message: message.client_id == 'client_encoded', presence_messages.items))[0] - self.assertEqual(message.data, {'foo': 'bar'}) + assert message.data == {'foo': 'bar'} def test_timestamp_is_datetime(self): presence_page = self.channel.presence.get() member = presence_page.items[0] - self.assertIsInstance(member.timestamp, datetime) + assert isinstance(member.timestamp, datetime) def test_presence_message_has_correct_member_key(self): presence_page = self.channel.presence.get() member = presence_page.items[0] - self.assertEqual(member.member_key, "%s:%s" % (member.connection_id, - member.client_id)) + assert member.member_key == "%s:%s" % (member.connection_id, member.client_id) def presence_mock_url(self): kwargs = { @@ -139,7 +133,7 @@ def test_get_presence_default_limit(self): url = self.presence_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.get() - self.assertNotIn('limit=', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=' not in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate @@ -147,14 +141,15 @@ def test_get_presence_with_limit(self): url = self.presence_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.get(300) - self.assertIn('limit=300', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=300' in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate def test_get_presence_max_limit_is_1000(self): url = self.presence_mock_url() self.responses_add_empty_msg_pack(url) - self.assertRaises(ValueError, self.channel.presence.get, 5000) + with pytest.raises(ValueError): + self.channel.presence.get(5000) @dont_vary_protocol @responses.activate @@ -162,7 +157,7 @@ def test_history_default_limit(self): url = self.history_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.history() - self.assertNotIn('limit=', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=' not in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate @@ -170,7 +165,7 @@ def test_history_with_limit(self): url = self.history_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.history(300) - self.assertIn('limit=300', responses.calls[0].request.url.split('?')[-1]) + assert 'limit=300' in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate @@ -178,14 +173,15 @@ def test_history_with_direction(self): url = self.history_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.history(direction='backwards') - self.assertIn('direction=backwards', responses.calls[0].request.url.split('?')[-1]) + assert 'direction=backwards' in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate def test_history_max_limit_is_1000(self): url = self.history_mock_url() self.responses_add_empty_msg_pack(url) - self.assertRaises(ValueError, self.channel.presence.history, 5000) + with pytest.raises(ValueError): + self.channel.presence.history(5000) @dont_vary_protocol @responses.activate @@ -193,8 +189,8 @@ def test_with_milisecond_start_end(self): url = self.history_mock_url() self.responses_add_empty_msg_pack(url) self.channel.presence.history(start=100000, end=100001) - self.assertIn('start=100000', responses.calls[0].request.url.split('?')[-1]) - self.assertIn('end=100001', responses.calls[0].request.url.split('?')[-1]) + assert 'start=100000' in responses.calls[0].request.url.split('?')[-1] + assert 'end=100001' in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate @@ -206,8 +202,8 @@ def test_with_timedate_startend(self): end_ms = start_ms + (1000 * 60 * 60) self.responses_add_empty_msg_pack(url) self.channel.presence.history(start=start, end=end) - self.assertIn('start=' + str(start_ms), responses.calls[0].request.url.split('?')[-1]) - self.assertIn('end=' + str(end_ms), responses.calls[0].request.url.split('?')[-1]) + assert 'start=' + str(start_ms) in responses.calls[0].request.url.split('?')[-1] + assert 'end=' + str(end_ms) in responses.calls[0].request.url.split('?')[-1] @dont_vary_protocol @responses.activate @@ -216,5 +212,5 @@ def test_with_start_gt_end(self): end = datetime(2015, 8, 15, 17, 11, 44, 706539) start = end + timedelta(hours=1) self.responses_add_empty_msg_pack(url) - with self.assertRaisesRegexp(ValueError, "'end' parameter has to be greater than or equal to 'start'"): + with pytest.raises(ValueError, match="'end' parameter has to be greater than or equal to 'start'"): self.channel.presence.history(start=start, end=end) From 02ec1364c762483c03db8b44c50f28752e22ff9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 23 Jul 2018 13:49:38 +0200 Subject: [PATCH 27/85] RSH1c1 New push.admin.channel_subscriptions.list --- ably/rest/push.py | 15 +++++++++++- ably/types/channelsubscription.py | 11 +++++++++ test/ably/restpush_test.py | 39 +++++++++++++++++++++++++++---- test/ably/utils.py | 3 +++ 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index cdb043c4..5a5a77e1 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,6 +1,6 @@ from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.device import DeviceDetails, make_device_details_response_processor -from ably.types.channelsubscription import PushChannelSubscription +from ably.types.channelsubscription import PushChannelSubscription, make_channel_subscriptions_response_processor class Push(object): @@ -131,6 +131,19 @@ def __init__(self, ably): def ably(self): return self.__ably + def list(self, **params): + """Returns a PaginatedResult object with the list of + PushChannelSubscription objects, filtered by the given parameters. + + :Parameters: + - `**params`: the parameters used to filter the list + """ + path = '/push/channelSubscriptions' + format_params(params) + response_processor = make_channel_subscriptions_response_processor( + self.ably.options.use_binary_protocol) + return PaginatedResult.paginated_query( + self.ably.http, url=path, response_processor=response_processor) + def save(self, subscription): """Creates or updates the subscription. Returns a PushChannelSubscription object. diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index 8cc9ca15..fe3c9da2 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -41,9 +41,20 @@ def from_dict(cls, obj): obj = {camel_to_snake(key): value for key, value in obj.items()} return cls(**obj) + @classmethod + def from_array(cls, array): + return [cls.from_dict(d) for d in array] + @classmethod def factory(cls, subscription): if isinstance(subscription, cls): return subscription return cls.from_dict(subscription) + + +def make_channel_subscriptions_response_processor(binary): + def channel_subscriptions_response_processor(response): + native = response.to_native() + return PushChannelSubscription.from_array(native) + return channel_subscriptions_response_processor diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 469ddf5c..db19d6cc 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -11,7 +11,7 @@ from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, BaseTestCase -from test.ably.utils import new_dict, random_string +from test.ably.utils import new_dict, random_string, get_random_key test_vars = RestSetup.get_test_vars() @@ -99,9 +99,10 @@ def remove_device_where(cls, **kw): return result - def get_device(self): - key = random.choice(list(self.devices.keys())) - return self.devices[key] + @classmethod + def get_device(cls): + key = get_random_key(cls.devices) + return cls.devices[key] # RSH1a def test_admin_publish(self): @@ -222,6 +223,36 @@ def test_admin_device_registrations_remove_where(self): # Remove with no matching params assert self.remove_device_where(clientId=device.client_id).status_code == 204 + # RSH1c1 + def test_admin_channel_subscriptions_list(self): + list_ = self.ably.push.admin.channel_subscriptions.list + + channel = 'canpublish:test1' + + # Register several channel subscriptions for later use + save = self.ably.push.admin.channel_subscriptions.save + for key in self.devices: + device = self.devices[key] + save(PushChannelSubscription(channel, device_id=device.id)) + + response = list_(channel=channel) + assert type(response) is PaginatedResult + assert type(response.items) is list + assert type(response.items[0]) is PushChannelSubscription + + # limit + assert len(list_(channel=channel, limit=5000).items) == len(self.devices) + assert len(list_(channel=channel, limit=2).items) == 2 + + # Filter by device id + device = self.get_device() + assert len(list_(channel=channel, deviceId=device.id).items) == 1 + assert len(list_(channel=channel, deviceId=self.get_device_id()).items) == 0 + + # Filter by client id + assert len(list_(channel=channel, clientId=device.client_id).items) == 0 + + # RSH1c3 def test_admin_channel_subscriptions_save(self): save = self.ably.push.admin.channel_subscriptions.save diff --git a/test/ably/utils.py b/test/ably/utils.py index 1f60384a..e1061bdd 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -126,3 +126,6 @@ def new_dict(src, **kw): new = src.copy() new.update(kw) return new + +def get_random_key(d): + return random.choice(list(d)) From ac078e9cb671bce43992a35d3a4cd7a6d67c4950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 23 Jul 2018 15:02:33 +0200 Subject: [PATCH 28/85] Pass flake8 --- test/ably/restpush_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index db19d6cc..85d17077 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -252,7 +252,6 @@ def test_admin_channel_subscriptions_list(self): # Filter by client id assert len(list_(channel=channel, clientId=device.client_id).items) == 0 - # RSH1c3 def test_admin_channel_subscriptions_save(self): save = self.ably.push.admin.channel_subscriptions.save From 07a8176e4b52f31b204fd61c78b81e22e4c28b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 12:20:54 +0200 Subject: [PATCH 29/85] travis pytest verbose --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f946ba50..633154de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ sudo: false install: - travis_retry pip install -r requirements-test.txt script: - - py.test --flake8 + - py.test --flake8 -v after_success: - "if [ $TRAVIS_PYTHON_VERSION == '3.6' ]; then pip install coveralls; coveralls; fi" From d4d04297779d826cb079509e51075ebec91b3e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 13:54:52 +0200 Subject: [PATCH 30/85] Fixing presence tests --- test/ably/restpresence_test.py | 76 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/test/ably/restpresence_test.py b/test/ably/restpresence_test.py index 2704c97b..7e53082d 100644 --- a/test/ably/restpresence_test.py +++ b/test/ably/restpresence_test.py @@ -21,18 +21,24 @@ @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestPresence(BaseTestCase): + @classmethod + def setUpClass(cls): + cls.ably = AblyRest(test_vars["keys"][0]["key_str"], + rest_host=test_vars["host"], + port=test_vars["port"], + tls_port=test_vars["tls_port"], + tls=test_vars["tls"]) + cls.channel = cls.ably.channels.get('persisted:presence_fixtures') + + @classmethod + def tearDownClass(cls): + cls.ably.channels.release('persisted:presence_fixtures') + def setUp(self): - self.ably = AblyRest(test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) - self.per_protocol_setup(True) + self.ably.options.use_binary_protocol = True def per_protocol_setup(self, use_binary_protocol): - # This will be called every test that vary by protocol for each protocol self.ably.options.use_binary_protocol = use_binary_protocol - self.channel = self.ably.channels.get('persisted:presence_fixtures') def test_channel_presence_get(self): presence_page = self.channel.presence.get() @@ -70,26 +76,6 @@ def test_presence_get_encoded(self): assert presence_history.items[-4].data == six.u('{ "test": "This is a JSONObject clientData payload"}') assert presence_history.items[-5].data == {"example": {"json": "Object"}} - def test_presence_history_encrypted(self): - key = b'0123456789abcdef' - self.ably.channels.release('persisted:presence_fixtures') - self.channel = self.ably.channels.get('persisted:presence_fixtures', - cipher={'key': key}) - presence_history = self.channel.presence.history() - assert presence_history.items[0].data == {'foo': 'bar'} - - def test_presence_get_encrypted(self): - key = b'0123456789abcdef' - self.ably.channels.release('persisted:presence_fixtures') - self.channel = self.ably.channels.get('persisted:presence_fixtures', - cipher={'key': key}) - presence_messages = self.channel.presence.get() - message = list(filter( - lambda message: message.client_id == 'client_encoded', - presence_messages.items))[0] - - assert message.data == {'foo': 'bar'} - def test_timestamp_is_datetime(self): presence_page = self.channel.presence.get() member = presence_page.items[0] @@ -214,3 +200,37 @@ def test_with_start_gt_end(self): self.responses_add_empty_msg_pack(url) with pytest.raises(ValueError, match="'end' parameter has to be greater than or equal to 'start'"): self.channel.presence.history(start=start, end=end) + + +@six.add_metaclass(VaryByProtocolTestsMetaclass) +class TestPresenceCrypt(BaseTestCase): + + @classmethod + def setUpClass(cls): + cls.ably = AblyRest(test_vars["keys"][0]["key_str"], + rest_host=test_vars["host"], + port=test_vars["port"], + tls_port=test_vars["tls_port"], + tls=test_vars["tls"]) + + key = b'0123456789abcdef' + cls.channel = cls.ably.channels.get('persisted:presence_fixtures', cipher={'key': key}) + + @classmethod + def tearDownClass(cls): + cls.ably.channels.release('persisted:presence_fixtures') + + def per_protocol_setup(self, use_binary_protocol): + self.ably.options.use_binary_protocol = use_binary_protocol + + def test_presence_history_encrypted(self): + presence_history = self.channel.presence.history() + assert presence_history.items[0].data == {'foo': 'bar'} + + def test_presence_get_encrypted(self): + presence_messages = self.channel.presence.get() + message = list(filter( + lambda message: message.client_id == 'client_encoded', + presence_messages.items))[0] + + assert message.data == {'foo': 'bar'} From 82fcb080d7824cf71e48465e97c852829842192c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 16:09:13 +0200 Subject: [PATCH 31/85] Fix setup/teardown package From nose to pytest --- test/ably/__init__.py | 8 -------- test/ably/conftest.py | 9 +++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 test/ably/conftest.py diff --git a/test/ably/__init__.py b/test/ably/__init__.py index ce458fe7..e69de29b 100644 --- a/test/ably/__init__.py +++ b/test/ably/__init__.py @@ -1,8 +0,0 @@ -from test.ably.restsetup import RestSetup - -def setup_package(): - RestSetup.get_test_vars() - -def teardown_package(): - RestSetup.clear_test_vars() - diff --git a/test/ably/conftest.py b/test/ably/conftest.py new file mode 100644 index 00000000..8bd1b41d --- /dev/null +++ b/test/ably/conftest.py @@ -0,0 +1,9 @@ +import pytest +from test.ably.restsetup import RestSetup + + +@pytest.fixture(scope='session', autouse=True) +def setup(): + RestSetup.get_test_vars() + yield + RestSetup.clear_test_vars() From 73f99414bc62df822e3b3533eae32e90886ffb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 18:46:42 +0200 Subject: [PATCH 32/85] Feedback from the review --- .travis.yml | 2 +- ably/rest/push.py | 14 ++++++-------- ably/rest/rest.py | 5 +---- ably/types/channelsubscription.py | 8 +++----- ably/types/device.py | 8 +++----- ably/types/stats.py | 8 +++----- test/ably/restpush_test.py | 9 ++++++++- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index 633154de..f946ba50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ sudo: false install: - travis_retry pip install -r requirements-test.txt script: - - py.test --flake8 -v + - py.test --flake8 after_success: - "if [ $TRAVIS_PYTHON_VERSION == '3.6' ]; then pip install coveralls; coveralls; fi" diff --git a/ably/rest/push.py b/ably/rest/push.py index 5a5a77e1..7a63b9ff 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,6 +1,6 @@ from ably.http.paginatedresult import PaginatedResult, format_params -from ably.types.device import DeviceDetails, make_device_details_response_processor -from ably.types.channelsubscription import PushChannelSubscription, make_channel_subscriptions_response_processor +from ably.types.device import DeviceDetails, device_details_response_processor +from ably.types.channelsubscription import PushChannelSubscription, channel_subscriptions_response_processor class Push(object): @@ -85,10 +85,9 @@ def list(self, **params): - `**params`: the parameters used to filter the list """ path = '/push/deviceRegistrations' + format_params(params) - response_processor = make_device_details_response_processor( - self.ably.options.use_binary_protocol) return PaginatedResult.paginated_query( - self.ably.http, url=path, response_processor=response_processor) + self.ably.http, url=path, + response_processor=device_details_response_processor) def save(self, device): """Creates or updates the device. Returns a DeviceDetails object. @@ -139,10 +138,9 @@ def list(self, **params): - `**params`: the parameters used to filter the list """ path = '/push/channelSubscriptions' + format_params(params) - response_processor = make_channel_subscriptions_response_processor( - self.ably.options.use_binary_protocol) return PaginatedResult.paginated_query( - self.ably.http, url=path, response_processor=response_processor) + self.ably.http, url=path, + response_processor=channel_subscriptions_response_processor) def save(self, subscription): """Creates or updates the subscription. Returns a diff --git a/ably/rest/rest.py b/ably/rest/rest.py index 41023fa7..915a5fc1 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -12,7 +12,7 @@ from ably.rest.push import Push from ably.util.exceptions import AblyException, catch_all from ably.types.options import Options -from ably.types.stats import make_stats_response_processor +from ably.types.stats import stats_response_processor from ably.types.tokendetails import TokenDetails log = logging.getLogger(__name__) @@ -89,9 +89,6 @@ def stats(self, direction=None, start=None, end=None, params=None, params = format_params(params, direction=direction, start=start, end=end, limit=limit, unit=unit) url = '/stats' + params - stats_response_processor = make_stats_response_processor( - self.options.use_binary_protocol) - return PaginatedResult.paginated_query( self.http, url=url, response_processor=stats_response_processor) diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index fe3c9da2..7022b81b 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -53,8 +53,6 @@ def factory(cls, subscription): return cls.from_dict(subscription) -def make_channel_subscriptions_response_processor(binary): - def channel_subscriptions_response_processor(response): - native = response.to_native() - return PushChannelSubscription.from_array(native) - return channel_subscriptions_response_processor +def channel_subscriptions_response_processor(response): + native = response.to_native() + return PushChannelSubscription.from_array(native) diff --git a/ably/types/device.py b/ably/types/device.py index 9f482068..35c7e583 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -101,8 +101,6 @@ def factory(cls, device): return cls.from_dict(device) -def make_device_details_response_processor(binary): - def device_details_response_processor(response): - native = response.to_native() - return DeviceDetails.from_array(native) - return device_details_response_processor +def device_details_response_processor(response): + native = response.to_native() + return DeviceDetails.from_array(native) diff --git a/ably/types/stats.py b/ably/types/stats.py index 2c39a3f9..b6e65195 100644 --- a/ably/types/stats.py +++ b/ably/types/stats.py @@ -150,11 +150,9 @@ def to_interval_id(date_time, granularity): return date_time.strftime(INTERVALS_FMT[granularity]) -def make_stats_response_processor(binary): - def stats_response_processor(response): - stats_array = response.to_native() - return Stats.from_array(stats_array) - return stats_response_processor +def stats_response_processor(response): + stats_array = response.to_native() + return Stats.from_array(stats_array) INTERVALS_FMT = { diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 85d17077..d815e749 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -230,10 +230,12 @@ def test_admin_channel_subscriptions_list(self): channel = 'canpublish:test1' # Register several channel subscriptions for later use + ids = set() save = self.ably.push.admin.channel_subscriptions.save for key in self.devices: device = self.devices[key] save(PushChannelSubscription(channel, device_id=device.id)) + ids.add(device.id) response = list_(channel=channel) assert type(response) is PaginatedResult @@ -246,7 +248,12 @@ def test_admin_channel_subscriptions_list(self): # Filter by device id device = self.get_device() - assert len(list_(channel=channel, deviceId=device.id).items) == 1 + items = list_(channel=channel, deviceId=device.id).items + assert len(items) == 1 + assert items[0].device_id == device.id + assert items[0].channel == channel + assert device.id in ids + assert len(list_(channel=channel, deviceId=self.get_device_id()).items) == 0 # Filter by client id From 84a4ba91d93e9a9b3035415a174fc7db55eac78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 26 Jul 2018 10:35:34 +0200 Subject: [PATCH 33/85] Reviewing history and presence code --- ably/rest/channel.py | 11 ++------- ably/types/message.py | 8 +----- ably/types/presence.py | 21 +++------------- test/ably/restchannelhistory_test.py | 37 +++++++++------------------- test/ably/utils.py | 5 ++++ 5 files changed, 22 insertions(+), 60 deletions(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index 176301c4..ed71e894 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -10,8 +10,7 @@ from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.message import ( - Message, make_message_response_handler, make_encrypted_message_response_handler, - MessageJSONEncoder) + Message, make_message_response_handler, MessageJSONEncoder) from ably.types.presence import Presence from ably.util.crypto import get_cipher from ably.util.exceptions import catch_all, IncompatibleClientIdException @@ -35,13 +34,7 @@ def history(self, direction=None, limit=None, start=None, end=None, timeout=None path = '/channels/%s/history' % self.__name path += params - if self.__cipher: - message_handler = make_encrypted_message_response_handler( - self.__cipher, self.ably.options.use_binary_protocol) - else: - message_handler = make_message_response_handler( - self.ably.options.use_binary_protocol) - + message_handler = make_message_response_handler(self.__cipher) return PaginatedResult.paginated_query( self.ably.http, url=path, response_processor=message_handler) diff --git a/ably/types/message.py b/ably/types/message.py index ef7beaca..de5db32f 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -224,14 +224,8 @@ def from_encoded(obj, cipher=None): **decoded_data ) -def make_message_response_handler(binary): - def message_response_handler(response): - messages = response.to_native() - return Message.from_encoded_array(messages) - return message_response_handler - -def make_encrypted_message_response_handler(cipher, binary): +def make_message_response_handler(cipher): def encrypted_message_response_handler(response): messages = response.to_native() return Message.from_encoded_array(messages, cipher=cipher) diff --git a/ably/types/presence.py b/ably/types/presence.py index 00fcf94b..a407dc7a 100644 --- a/ably/types/presence.py +++ b/ably/types/presence.py @@ -120,11 +120,7 @@ def get(self, limit=None): qs['limit'] = limit path = self._path_with_qs('%s/presence' % self.__base_path.rstrip('/'), qs) - if self.__cipher: - presence_handler = make_encrypted_presence_response_handler(self.__cipher, self.__binary) - else: - presence_handler = make_presence_response_handler(self.__binary) - + presence_handler = make_presence_response_handler(self.__cipher) return PaginatedResult.paginated_query( self.__http, url=path, response_processor=presence_handler) @@ -152,23 +148,12 @@ def history(self, limit=None, direction=None, start=None, end=None): path = self._path_with_qs('%s/presence/history' % self.__base_path.rstrip('/'), qs) - if self.__cipher: - presence_handler = make_encrypted_presence_response_handler( - self.__cipher, self.__binary) - else: - presence_handler = make_presence_response_handler(self.__binary) - + presence_handler = make_presence_response_handler(self.__cipher) return PaginatedResult.paginated_query( self.__http, url=path, response_processor=presence_handler) -def make_presence_response_handler(binary): - def presence_response_handler(response): - messages = response.to_native() - return PresenceMessage.from_encoded_array(messages) - return presence_response_handler - -def make_encrypted_presence_response_handler(cipher, binary): +def make_presence_response_handler(cipher): def encrypted_presence_response_handler(response): messages = response.to_native() return PresenceMessage.from_encoded_array(messages, cipher=cipher) diff --git a/test/ably/restchannelhistory_test.py b/test/ably/restchannelhistory_test.py index 6837d618..26ba0b49 100644 --- a/test/ably/restchannelhistory_test.py +++ b/test/ably/restchannelhistory_test.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import logging -import time import pytest import responses @@ -28,15 +27,12 @@ def setUpClass(cls): port=test_vars["port"], tls_port=test_vars["tls_port"], tls=test_vars["tls"]) - cls.time_offset = cls.ably.time() - int(time.time()) def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol - self.use_binary_protocol = use_binary_protocol def test_channel_history_types(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_types')] + history0 = self.get_channel('persisted:channelhistory_types') history0.publish('history0', six.u('This is a string message payload')) history0.publish('history1', b'This is a byte[] message payload') @@ -68,8 +64,7 @@ def test_channel_history_types(self): assert expected_message_history == messages, "Expect messages in reverse order" def test_channel_history_multi_50_forwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_multi_50_f')] + history0 = self.get_channel('persisted:channelhistory_multi_50_f') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -84,8 +79,7 @@ def test_channel_history_multi_50_forwards(self): assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_multi_50_backwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_multi_50_b')] + history0 = self.get_channel('persisted:channelhistory_multi_50_b') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -142,8 +136,7 @@ def test_channel_history_max_limit_is_1000(self): channel.history(limit=1001) def test_channel_history_limit_forwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_limit_f')] + history0 = self.get_channel('persisted:channelhistory_limit_f') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -158,8 +151,7 @@ def test_channel_history_limit_forwards(self): assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_limit_backwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_limit_b')] + history0 = self.get_channel('persisted:channelhistory_limit_b') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -174,8 +166,7 @@ def test_channel_history_limit_backwards(self): assert messages == expected_messages, 'Expect messages in forward order' def test_channel_history_time_forwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_time_f')] + history0 = self.get_channel('persisted:channelhistory_time_f') for i in range(20): history0.publish('history%d' % i, str(i)) @@ -201,8 +192,7 @@ def test_channel_history_time_forwards(self): assert expected_messages == messages, 'Expect messages in forward order' def test_channel_history_time_backwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_time_b')] + history0 = self.get_channel('persisted:channelhistory_time_b') for i in range(20): history0.publish('history%d' % i, str(i)) @@ -228,8 +218,7 @@ def test_channel_history_time_backwards(self): assert expected_messages, messages == 'Expect messages in reverse order' def test_channel_history_paginate_forwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_paginate_f')] + history0 = self.get_channel('persisted:channelhistory_paginate_f') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -260,8 +249,7 @@ def test_channel_history_paginate_forwards(self): assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_backwards(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_paginate_b')] + history0 = self.get_channel('persisted:channelhistory_paginate_b') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -291,9 +279,7 @@ def test_channel_history_paginate_backwards(self): assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_forwards_first(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_paginate_first_f')] - + history0 = self.get_channel('persisted:channelhistory_paginate_first_f') for i in range(50): history0.publish('history%d' % i, str(i)) @@ -322,8 +308,7 @@ def test_channel_history_paginate_forwards_first(self): assert expected_messages == messages, 'Expected 10 messages' def test_channel_history_paginate_backwards_rel_first(self): - history0 = self.ably.channels[ - self.get_channel_name('persisted:channelhistory_paginate_first_b')] + history0 = self.get_channel('persisted:channelhistory_paginate_first_b') for i in range(50): history0.publish('history%d' % i, str(i)) diff --git a/test/ably/utils.py b/test/ably/utils.py index e1061bdd..2656576d 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -20,6 +20,11 @@ def responses_add_empty_msg_pack(self, url, method=responses.GET): def get_channel_name(cls, prefix=''): return prefix + random_string(10) + @classmethod + def get_channel(cls, prefix=''): + name = cls.get_channel_name(prefix) + return cls.ably.channels.get(name) + def assert_responses_type(protocol): """ From 0ac91dea7f000563e2660ff51282d46cb1704719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 11:22:04 +0200 Subject: [PATCH 34/85] Fix tests Started failing with deviceIdentityToken error --- ably/types/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ably/types/utils.py b/ably/types/utils.py index b3d83c08..28d80374 100644 --- a/ably/types/utils.py +++ b/ably/types/utils.py @@ -1,7 +1,12 @@ import re -def camel_to_snake(name, first_cap_re = re.compile('(.)([A-Z][a-z]+)')): - return first_cap_re.sub(r'\1_\2', name).lower() + +first_cap_re = re.compile('(.)([A-Z][a-z]+)') +all_cap_re = re.compile('([a-z0-9])([A-Z])') +def camel_to_snake(name): + s1 = first_cap_re.sub(r'\1_\2', name) + return all_cap_re.sub(r'\1_\2', s1).lower() + def snake_to_camel(name): name = name.split('_') From d1a821aa96e641c77d91c31dd9c0cbf1b9f606a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 11:55:08 +0200 Subject: [PATCH 35/85] Replace self.assertXXX by assert in a couple of files --- test/ably/resttime_test.py | 11 +++++------ test/ably/utils.py | 8 +++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/test/ably/resttime_test.py b/test/ably/resttime_test.py index e51a89b9..efb9c652 100644 --- a/test/ably/resttime_test.py +++ b/test/ably/resttime_test.py @@ -2,11 +2,11 @@ import time +import pytest import six from ably import AblyException from ably import AblyRest -from ably import Options from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseTestCase @@ -32,8 +32,7 @@ def test_time_accuracy(self): actual_time = time.time() * 1000.0 seconds = 10 - self.assertLess(abs(actual_time - reported_time), seconds * 1000, - msg="Time is not within %s seconds" % seconds) + assert abs(actual_time - reported_time) < seconds * 1000, "Time is not within %s seconds" % seconds def test_time_without_key_or_token(self): ably = AblyRest(token='foo', @@ -47,8 +46,7 @@ def test_time_without_key_or_token(self): actual_time = time.time() * 1000.0 seconds = 10 - self.assertLess(abs(actual_time - reported_time), seconds * 1000, - msg="Time is not within %s seconds" % seconds) + assert abs(actual_time - reported_time) < seconds * 1000, "Time is not within %s seconds" % seconds @dont_vary_protocol def test_time_fails_without_valid_host(self): @@ -57,4 +55,5 @@ def test_time_fails_without_valid_host(self): port=test_vars["port"], tls_port=test_vars["tls_port"]) - self.assertRaises(AblyException, ably.time) + with pytest.raises(AblyException): + ably.time() diff --git a/test/ably/utils.py b/test/ably/utils.py index 2656576d..d288ba78 100644 --- a/test/ably/utils.py +++ b/test/ably/utils.py @@ -62,16 +62,14 @@ def test_decorated(self, *args, **kwargs): patcher = patch() fn(self, *args, **kwargs) unpatch(patcher) - self.assertGreaterEqual(len(responses), 1, - "If your test doesn't make any requests," - " use the @dont_vary_protocol decorator") + assert len(responses) >= 1, "If your test doesn't make any requests, use the @dont_vary_protocol decorator" for response in responses: if protocol == 'json': - self.assertEquals(response.headers['content-type'], 'application/json') + assert response.headers['content-type'] == 'application/json' if response.content: response.json() else: - self.assertEquals(response.headers['content-type'], 'application/x-msgpack') + assert response.headers['content-type'] == 'application/x-msgpack' if response.content: msgpack.unpackb(response.content, encoding='utf-8') From df08ca0d0870947bfcc4e82ec1a693206ba5c108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 12:32:57 +0200 Subject: [PATCH 36/85] tests: use assert in restrequest_test --- test/ably/restrequest_test.py | 61 ++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index e7fe5a20..3c1e473b 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -1,3 +1,4 @@ +import pytest import requests import six @@ -48,49 +49,49 @@ def test_get(self): params = {'limit': 10, 'direction': 'forwards'} result = self.ably.request('GET', self.path, params=params) - self.assertIsInstance(result, HttpPaginatedResponse) # RSC19d + assert isinstance(result, HttpPaginatedResponse) # RSC19d # HP2 - self.assertIsInstance(result.next(), HttpPaginatedResponse) - self.assertIsInstance(result.first(), HttpPaginatedResponse) + assert isinstance(result.next(), HttpPaginatedResponse) + assert isinstance(result.first(), HttpPaginatedResponse) # HP3 - self.assertIsInstance(result.items, list) + assert isinstance(result.items, list) item = result.items[0] - self.assertIsInstance(item, dict) - self.assertIn('timestamp', item) - self.assertIn('id', item) - self.assertEqual(item['name'], 'event0') - self.assertEqual(item['data'], 'lorem ipsum 0') - - self.assertEqual(result.status_code, 200) # HP4 - self.assertEqual(result.success, True) # HP5 - self.assertEqual(result.error_code, None) # HP6 - self.assertEqual(result.error_message, None) # HP7 - self.assertIsInstance(result.headers, list) # HP7 + assert isinstance(item, dict) + assert 'timestamp' in item + assert 'id' in item + assert item['name'] == 'event0' + assert item['data'] == 'lorem ipsum 0' + + assert result.status_code == 200 # HP4 + assert result.success == True # HP5 + assert result.error_code == None # HP6 + assert result.error_message == None # HP7 + assert isinstance(result.headers, list) # HP7 @dont_vary_protocol def test_not_found(self): result = self.ably.request('GET', '/not-found') - self.assertIsInstance(result, HttpPaginatedResponse) # RSC19d - self.assertEqual(result.status_code, 404) # HP4 - self.assertEqual(result.success, False) # HP5 + assert isinstance(result, HttpPaginatedResponse) # RSC19d + assert result.status_code == 404 # HP4 + assert result.success == False # HP5 @dont_vary_protocol def test_error(self): params = {'limit': 'abc'} result = self.ably.request('GET', self.path, params=params) - self.assertIsInstance(result, HttpPaginatedResponse) # RSC19d - self.assertEqual(result.status_code, 400) # HP4 - self.assertFalse(result.success) - self.assertTrue(result.error_code) - self.assertTrue(result.error_message) + assert isinstance(result, HttpPaginatedResponse) # RSC19d + assert result.status_code == 400 # HP4 + assert not result.success + assert result.error_code + assert result.error_message def test_headers(self): key = 'X-Test' value = 'lorem ipsum' result = self.ably.request('GET', '/time', headers={key: value}) - self.assertEqual(result.response.request.headers[key], value) + assert result.response.request.headers[key] == value # RSC19e @dont_vary_protocol @@ -98,8 +99,8 @@ def test_timeout(self): # Timeout timeout = 0.000001 ably = AblyRest(token="foo", http_request_timeout=timeout) - self.assertEqual(ably.http.http_request_timeout, timeout) - with self.assertRaises(requests.exceptions.ReadTimeout): + assert ably.http.http_request_timeout == timeout + with pytest.raises(requests.exceptions.ReadTimeout): ably.request('GET', '/time') # Bad host, use fallback @@ -110,9 +111,9 @@ def test_timeout(self): tls=test_vars["tls"], fallback_hosts_use_default=True) result = ably.request('GET', '/time') - self.assertIsInstance(result, HttpPaginatedResponse) - self.assertEqual(len(result.items), 1) - self.assertIsInstance(result.items[0], int) + assert isinstance(result, HttpPaginatedResponse) + assert len(result.items) == 1 + assert isinstance(result.items[0], int) # Bad host, no Fallback ably = AblyRest(key=test_vars["keys"][0]["key_str"], @@ -120,5 +121,5 @@ def test_timeout(self): port=test_vars["port"], tls_port=test_vars["tls_port"], tls=test_vars["tls"]) - with self.assertRaises(requests.exceptions.ConnectionError): + with pytest.raises(requests.exceptions.ConnectionError): ably.request('GET', '/time') From 3f34a726a8a17a828be67a63d46eb77d131227d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 12:40:09 +0200 Subject: [PATCH 37/85] Fix flake8 --- test/ably/restrequest_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index 3c1e473b..bb2f07ca 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -65,9 +65,9 @@ def test_get(self): assert item['data'] == 'lorem ipsum 0' assert result.status_code == 200 # HP4 - assert result.success == True # HP5 - assert result.error_code == None # HP6 - assert result.error_message == None # HP7 + assert result.success is True # HP5 + assert result.error_code is None # HP6 + assert result.error_message is None # HP7 assert isinstance(result.headers, list) # HP7 @dont_vary_protocol @@ -75,7 +75,7 @@ def test_not_found(self): result = self.ably.request('GET', '/not-found') assert isinstance(result, HttpPaginatedResponse) # RSC19d assert result.status_code == 404 # HP4 - assert result.success == False # HP5 + assert result.success is False # HP5 @dont_vary_protocol def test_error(self): From 1b229c2adc72c185f12bada7a7636fff49b1cf4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 12:48:51 +0200 Subject: [PATCH 38/85] tests: use assert in restpaginatedresult_test --- test/ably/restpaginatedresult_test.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/ably/restpaginatedresult_test.py b/test/ably/restpaginatedresult_test.py index 2ec3cedb..07ebf398 100644 --- a/test/ably/restpaginatedresult_test.py +++ b/test/ably/restpaginatedresult_test.py @@ -69,24 +69,24 @@ def tearDown(self): responses.reset() def test_items(self): - self.assertEquals(len(self.paginated_result.items), 2) + assert len(self.paginated_result.items) == 2 def test_with_no_headers(self): - self.assertIsNone(self.paginated_result.first()) - self.assertIsNone(self.paginated_result.next()) - self.assertTrue(self.paginated_result.is_last()) + assert self.paginated_result.first() is None + assert self.paginated_result.next() is None + assert self.paginated_result.is_last() def test_with_next(self): pag = self.paginated_result_with_headers - self.assertTrue(pag.has_next()) - self.assertFalse(pag.is_last()) + assert pag.has_next() + assert not pag.is_last() def test_first(self): pag = self.paginated_result_with_headers pag = pag.first() - self.assertEquals(pag.items[0]['page'], 1) + assert pag.items[0]['page'] == 1 def test_next(self): pag = self.paginated_result_with_headers pag = pag.next() - self.assertEquals(pag.items[0]['page'], 2) + assert pag.items[0]['page'] == 2 From 63e3abe410286ab754c7d2b0328d59e5fdccc3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 13:04:37 +0200 Subject: [PATCH 39/85] tests: use assert in restchannels_test --- test/ably/restchannels_test.py | 51 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/test/ably/restchannels_test.py b/test/ably/restchannels_test.py index f4318c1d..78c8eeb9 100644 --- a/test/ably/restchannels_test.py +++ b/test/ably/restchannels_test.py @@ -2,11 +2,11 @@ import collections +import pytest from six.moves import range from ably import AblyRest, AblyException from ably.rest.channel import Channel, Channels, Presence -from ably.types.capability import Capability from ably.util.crypto import generate_random_key from test.ably.restsetup import RestSetup @@ -26,73 +26,73 @@ def setUp(self): tls=test_vars["tls"]) def test_rest_channels_attr(self): - self.assertTrue(hasattr(self.ably, 'channels')) - self.assertIsInstance(self.ably.channels, Channels) + assert hasattr(self.ably, 'channels') + assert isinstance(self.ably.channels, Channels) def test_channels_get_returns_new_or_existing(self): channel = self.ably.channels.get('new_channel') - self.assertIsInstance(channel, Channel) + assert isinstance(channel, Channel) channel_same = self.ably.channels.get('new_channel') - self.assertIs(channel, channel_same) + assert channel is channel_same def test_channels_get_returns_new_with_options(self): key = generate_random_key() channel = self.ably.channels.get('new_channel', cipher={'key': key}) - self.assertIsInstance(channel, Channel) - self.assertIs(channel.cipher.secret_key, key) + assert isinstance(channel, Channel) + assert channel.cipher.secret_key is key def test_channels_get_updates_existing_with_options(self): key = generate_random_key() channel = self.ably.channels.get('new_channel', cipher={'key': key}) - self.assertIsNot(channel.cipher, None) + assert channel.cipher is not None channel_same = self.ably.channels.get('new_channel', cipher=None) - self.assertIs(channel, channel_same) - self.assertIs(channel.cipher, None) + assert channel is channel_same + assert channel.cipher is None def test_channels_get_doesnt_updates_existing_with_none_options(self): key = generate_random_key() channel = self.ably.channels.get('new_channel', cipher={'key': key}) - self.assertIsNot(channel.cipher, None) + assert channel.cipher is not None channel_same = self.ably.channels.get('new_channel') - self.assertIs(channel, channel_same) - self.assertIsNot(channel.cipher, None) + assert channel is channel_same + assert channel.cipher is not None def test_channels_in(self): - self.assertTrue('new_channel' not in self.ably.channels) + assert 'new_channel' not in self.ably.channels self.ably.channels.get('new_channel') new_channel_2 = self.ably.channels.get('new_channel_2') - self.assertTrue('new_channel' in self.ably.channels) - self.assertTrue(new_channel_2 in self.ably.channels) + assert 'new_channel' in self.ably.channels + assert new_channel_2 in self.ably.channels def test_channels_iteration(self): channel_names = ['channel_{}'.format(i) for i in range(5)] [self.ably.channels.get(name) for name in channel_names] - self.assertIsInstance(self.ably.channels, collections.Iterable) + assert isinstance(self.ably.channels, collections.Iterable) for name, channel in zip(channel_names, self.ably.channels): - self.assertIsInstance(channel, Channel) - self.assertEqual(name, channel.name) + assert isinstance(channel, Channel) + assert name == channel.name def test_channels_release(self): self.ably.channels.get('new_channel') self.ably.channels.release('new_channel') - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.ably.channels.release('new_channel') def test_channels_del(self): self.ably.channels.get('new_channel') del self.ably.channels['new_channel'] - with self.assertRaises(KeyError): + with pytest.raises(KeyError): del self.ably.channels['new_channel'] def test_channel_has_presence(self): channel = self.ably.channels.get('new_channnel') - self.assertTrue(channel.presence) - self.assertTrue(isinstance(channel.presence, Presence)) + assert channel.presence + assert isinstance(channel.presence, Presence) def test_without_permissions(self): key = test_vars["keys"][2] @@ -101,8 +101,7 @@ def test_without_permissions(self): port=test_vars["port"], tls_port=test_vars["tls_port"], tls=test_vars["tls"]) - with self.assertRaises(AblyException) as cm: + with pytest.raises(AblyException) as excinfo: ably.channels['test_publish_without_permission'].publish('foo', 'woop') - the_exception = cm.exception - self.assertIn('not permitted', the_exception.message) + assert 'not permitted' in excinfo.value.message From 3302b183014ad70ff1f69f2095c99fbaca1158fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 13:23:17 +0200 Subject: [PATCH 40/85] tests: use assert in resthttp_test --- test/ably/resthttp_test.py | 69 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index cdc78f8b..a6836bbb 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -1,8 +1,10 @@ from __future__ import absolute_import +import re import time import mock +import pytest import requests from six.moves.urllib.parse import urljoin @@ -19,25 +21,24 @@ class TestRestHttp(BaseTestCase): def test_max_retry_attempts_and_timeouts_defaults(self): ably = AblyRest(token="foo") - self.assertIn('http_open_timeout', ably.http.CONNECTION_RETRY_DEFAULTS) - self.assertIn('http_request_timeout', ably.http.CONNECTION_RETRY_DEFAULTS) + assert 'http_open_timeout' in ably.http.CONNECTION_RETRY_DEFAULTS + assert 'http_request_timeout' in ably.http.CONNECTION_RETRY_DEFAULTS with mock.patch('requests.sessions.Session.send', side_effect=requests.exceptions.RequestException) as send_mock: - with self.assertRaises(requests.exceptions.RequestException): + with pytest.raises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) - self.assertEqual( - send_mock.call_count, - Defaults.http_max_retry_count) - self.assertEqual( - send_mock.call_args, - mock.call(mock.ANY, timeout=(ably.http.CONNECTION_RETRY_DEFAULTS['http_open_timeout'], - ably.http.CONNECTION_RETRY_DEFAULTS['http_request_timeout']))) + assert send_mock.call_count == Defaults.http_max_retry_count + timeout = ( + ably.http.CONNECTION_RETRY_DEFAULTS['http_open_timeout'], + ably.http.CONNECTION_RETRY_DEFAULTS['http_request_timeout'], + ) + assert send_mock.call_args == mock.call(mock.ANY, timeout=timeout) def test_cumulative_timeout(self): ably = AblyRest(token="foo") - self.assertIn('http_max_retry_duration', ably.http.CONNECTION_RETRY_DEFAULTS) + assert 'http_max_retry_duration' in ably.http.CONNECTION_RETRY_DEFAULTS ably.options.http_max_retry_duration = 0.5 @@ -47,10 +48,10 @@ def sleep_and_raise(*args, **kwargs): with mock.patch('requests.sessions.Session.send', side_effect=sleep_and_raise) as send_mock: - with self.assertRaises(requests.exceptions.RequestException): + with pytest.raises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) - self.assertEqual(send_mock.call_count, 1) + assert send_mock.call_count == 1 def test_host_fallback(self): ably = AblyRest(token="foo") @@ -64,19 +65,17 @@ def make_url(host): with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('requests.sessions.Session.send', side_effect=requests.exceptions.RequestException) as send_mock: - with self.assertRaises(requests.exceptions.RequestException): + with pytest.raises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) - self.assertEqual( - send_mock.call_count, - Defaults.http_max_retry_count) + assert send_mock.call_count == Defaults.http_max_retry_count expected_urls_set = set([ make_url(host) for host in Options(http_max_retry_count=10).get_rest_hosts() ]) for ((__, url), ___) in request_mock.call_args_list: - self.assertIn(url, expected_urls_set) + assert url in expected_urls_set expected_urls_set.remove(url) def test_no_host_fallback_nor_retries_if_custom_host(self): @@ -91,13 +90,11 @@ def test_no_host_fallback_nor_retries_if_custom_host(self): with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('requests.sessions.Session.send', side_effect=requests.exceptions.RequestException) as send_mock: - with self.assertRaises(requests.exceptions.RequestException): + with pytest.raises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) - self.assertEqual(send_mock.call_count, 1) - self.assertEqual( - request_mock.call_args, - mock.call(mock.ANY, custom_url, data=mock.ANY, headers=mock.ANY)) + assert send_mock.call_count == 1 + assert request_mock.call_args == mock.call(mock.ANY, custom_url, data=mock.ANY, headers=mock.ANY) def test_no_retry_if_not_500_to_599_http_code(self): default_host = Options().get_rest_host() @@ -116,23 +113,21 @@ def raise_ably_exception(*args, **kwagrs): with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('ably.util.exceptions.AblyException.raise_for_response', side_effect=raise_ably_exception) as send_mock: - with self.assertRaises(AblyException): + with pytest.raises(AblyException): ably.http.make_request('GET', '/', skip_auth=True) - self.assertEqual(send_mock.call_count, 1) - self.assertEqual( - request_mock.call_args, - mock.call(mock.ANY, default_url, data=mock.ANY, headers=mock.ANY)) + assert send_mock.call_count == 1 + assert request_mock.call_args == mock.call(mock.ANY, default_url, data=mock.ANY, headers=mock.ANY) def test_custom_http_timeouts(self): ably = AblyRest( token="foo", http_request_timeout=30, http_open_timeout=8, http_max_retry_count=6, http_max_retry_duration=20) - self.assertEqual(ably.http.http_request_timeout, 30) - self.assertEqual(ably.http.http_open_timeout, 8) - self.assertEqual(ably.http.http_max_retry_count, 6) - self.assertEqual(ably.http.http_max_retry_duration, 20) + assert ably.http.http_request_timeout == 30 + assert ably.http.http_open_timeout == 8 + assert ably.http.http_max_retry_count == 6 + assert ably.http.http_max_retry_duration == 20 # RSC7a, RSC7b def test_request_headers(self): @@ -144,16 +139,16 @@ def test_request_headers(self): r = ably.http.make_request('HEAD', '/time', skip_auth=True) # API - self.assertIn('X-Ably-Version', r.request.headers) - self.assertEqual(r.request.headers['X-Ably-Version'], '1.0') + assert 'X-Ably-Version' in r.request.headers + assert r.request.headers['X-Ably-Version'] == '1.0' # Lib - self.assertIn('X-Ably-Lib', r.request.headers) + assert 'X-Ably-Lib' in r.request.headers expr = r"^python-1\.0\.\d+(-\w+)?$" - self.assertRegexpMatches(r.request.headers['X-Ably-Lib'], expr) + assert re.search(expr, r.request.headers['X-Ably-Lib']) # Lib Variant ably.set_variant('django') r = ably.http.make_request('HEAD', '/time', skip_auth=True) expr = r"^python.django-1\.0\.\d+(-\w+)?$" - self.assertRegexpMatches(r.request.headers['X-Ably-Lib'], expr) + assert re.search(expr, r.request.headers['X-Ably-Lib']) From 73d8682cb6f499dc3e50ad6e63e89829715c3c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 13:39:06 +0200 Subject: [PATCH 41/85] tests: use assert in restcapability_test --- test/ably/restcapability_test.py | 94 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/test/ably/restcapability_test.py b/test/ably/restcapability_test.py index 66bd9f66..aae175d4 100644 --- a/test/ably/restcapability_test.py +++ b/test/ably/restcapability_test.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import pytest import six from ably import AblyRest @@ -30,9 +31,8 @@ def test_blanket_intersection_with_key(self): token_details = self.ably.auth.request_token(key_name=key['key_name'], key_secret=key['key_secret']) expected_capability = Capability(key["capability"]) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability.") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability." def test_equal_intersection_with_key(self): key = test_vars['keys'][1] @@ -44,25 +44,26 @@ def test_equal_intersection_with_key(self): expected_capability = Capability(key["capability"]) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" @dont_vary_protocol def test_empty_ops_intersection(self): key = test_vars['keys'][1] - self.assertRaises(AblyException, self.ably.auth.request_token, - key_name=key['key_name'], - key_secret=key['key_secret'], - token_params={'capability': {'testchannel': ['subscribe']}}) + with pytest.raises(AblyException): + self.ably.auth.request_token( + key_name=key['key_name'], + key_secret=key['key_secret'], + token_params={'capability': {'testchannel': ['subscribe']}}) @dont_vary_protocol def test_empty_paths_intersection(self): key = test_vars['keys'][1] - self.assertRaises(AblyException, self.ably.auth.request_token, - key_name=key['key_name'], - key_secret=key['key_secret'], - token_params={'capability': {"testchannelx": ["publish"]}}) + with pytest.raises(AblyException): + self.ably.auth.request_token( + key_name=key['key_name'], + key_secret=key['key_secret'], + token_params={'capability': {"testchannelx": ["publish"]}}) def test_non_empty_ops_intersection(self): key = test_vars['keys'][4] @@ -81,9 +82,8 @@ def test_non_empty_ops_intersection(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_non_empty_paths_intersection(self): key = test_vars['keys'][4] @@ -105,9 +105,8 @@ def test_non_empty_paths_intersection(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_wildcard_ops_intersection(self): key = test_vars['keys'][4] @@ -128,9 +127,8 @@ def test_wildcard_ops_intersection(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_wildcard_ops_intersection_2(self): key = test_vars['keys'][4] @@ -151,9 +149,8 @@ def test_wildcard_ops_intersection_2(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_wildcard_resources_intersection(self): key = test_vars['keys'][2] @@ -174,9 +171,8 @@ def test_wildcard_resources_intersection(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_wildcard_resources_intersection_2(self): key = test_vars['keys'][2] @@ -197,9 +193,8 @@ def test_wildcard_resources_intersection_2(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" def test_wildcard_resources_intersection_3(self): key = test_vars['keys'][2] @@ -221,36 +216,35 @@ def test_wildcard_resources_intersection_3(self): token_details = self.ably.auth.request_token(token_params, **kwargs) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(expected_capability, token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert expected_capability == token_details.capability, "Unexpected capability" @dont_vary_protocol def test_invalid_capabilities(self): - with self.assertRaises(AblyException) as cm: - token_details = self.ably.auth.request_token( + with pytest.raises(AblyException) as excinfo: + self.ably.auth.request_token( token_params={'capability': {"channel0": ["publish_"]}}) - the_exception = cm.exception - self.assertEqual(400, the_exception.status_code) - self.assertEqual(40000, the_exception.code) + the_exception = excinfo.value + assert 400 == the_exception.status_code + assert 40000 == the_exception.code @dont_vary_protocol def test_invalid_capabilities_2(self): - with self.assertRaises(AblyException) as cm: - token_details = self.ably.auth.request_token( + with pytest.raises(AblyException) as excinfo: + self.ably.auth.request_token( token_params={'capability': {"channel0": ["*", "publish"]}}) - the_exception = cm.exception - self.assertEqual(400, the_exception.status_code) - self.assertEqual(40000, the_exception.code) + the_exception = excinfo.value + assert 400 == the_exception.status_code + assert 40000 == the_exception.code @dont_vary_protocol def test_invalid_capabilities_3(self): - with self.assertRaises(AblyException) as cm: - token_details = self.ably.auth.request_token( + with pytest.raises(AblyException) as excinfo: + self.ably.auth.request_token( token_params={'capability': {"channel0": []}}) - the_exception = cm.exception - self.assertEqual(400, the_exception.status_code) - self.assertEqual(40000, the_exception.code) + the_exception = excinfo.value + assert 400 == the_exception.status_code + assert 40000 == the_exception.code From e55f2740c50eec7f0fdd7c013eac8a2fa36e1668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 13:48:34 +0200 Subject: [PATCH 42/85] tests: use assert in restcrypto_test --- test/ably/restcrypto_test.py | 92 +++++++++++++----------------------- 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/test/ably/restcrypto_test.py b/test/ably/restcrypto_test.py index 391d446b..3b212999 100644 --- a/test/ably/restcrypto_test.py +++ b/test/ably/restcrypto_test.py @@ -68,7 +68,7 @@ def test_cbc_channel_cipher(self): actual_ciphertext = cipher.encrypt(plaintext) - self.assertEqual(expected_ciphertext, actual_ciphertext) + assert expected_ciphertext == actual_ciphertext def test_crypto_publish(self): channel_name = self.get_channel_name('persisted:crypto_publish_text') @@ -81,24 +81,16 @@ def test_crypto_publish(self): history = publish0.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(4, len(messages), msg="Expected 4 messages") + assert messages is not None, "Expected non-None messages" + assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) log.debug("message_contents: %s" % str(message_contents)) - self.assertEqual(six.u("This is a string message payload"), - message_contents["publish3"], - msg="Expect publish3 to be expected String)") - self.assertEqual(b"This is a byte[] message payload", - message_contents["publish4"], - msg="Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4'])) - self.assertEqual({"test": "This is a JSONObject message payload"}, - message_contents["publish5"], - msg="Expect publish5 to be expected JSONObject") - self.assertEqual(["This is a JSONArray message payload"], - message_contents["publish6"], - msg="Expect publish6 to be expected JSONObject") + assert six.u("This is a string message payload") == message_contents["publish3"], "Expect publish3 to be expected String)" + assert b"This is a byte[] message payload" == message_contents["publish4"], "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"], "Expect publish5 to be expected JSONObject" + assert ["This is a JSONArray message payload"] == message_contents["publish6"], "Expect publish6 to be expected JSONObject" def test_crypto_publish_256(self): rndfile = Random.new() @@ -115,24 +107,16 @@ def test_crypto_publish_256(self): history = publish0.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(4, len(messages), msg="Expected 4 messages") + assert messages is not None, "Expected non-None messages" + assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) log.debug("message_contents: %s" % str(message_contents)) - self.assertEqual(six.u("This is a string message payload"), - message_contents["publish3"], - msg="Expect publish3 to be expected String)") - self.assertEqual(b"This is a byte[] message payload", - message_contents["publish4"], - msg="Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4'])) - self.assertEqual({"test": "This is a JSONObject message payload"}, - message_contents["publish5"], - msg="Expect publish5 to be expected JSONObject") - self.assertEqual(["This is a JSONArray message payload"], - message_contents["publish6"], - msg="Expect publish6 to be expected JSONObject") + assert six.u("This is a string message payload") == message_contents["publish3"], "Expect publish3 to be expected String)" + assert b"This is a byte[] message payload" == message_contents["publish4"], "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"], "Expect publish5 to be expected JSONObject" + assert ["This is a JSONArray message payload"] == message_contents["publish6"], "Expect publish6 to be expected JSONObject" def test_crypto_publish_key_mismatch(self): channel_name = self.get_channel_name('persisted:crypto_publish_key_mismatch') @@ -169,24 +153,16 @@ def test_crypto_send_unencrypted(self): history = rx_channel.history() messages = history.items - self.assertIsNotNone(messages, msg="Expected non-None messages") - self.assertEqual(4, len(messages), msg="Expected 4 messages") + assert messages is not None, "Expected non-None messages" + assert 4 == len(messages), "Expected 4 messages" message_contents = dict((m.name, m.data) for m in messages) log.debug("message_contents: %s" % str(message_contents)) - self.assertEqual(six.u("This is a string message payload"), - message_contents["publish3"], - msg="Expect publish3 to be expected String)") - self.assertEqual(b"This is a byte[] message payload", - message_contents["publish4"], - msg="Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4'])) - self.assertEqual({"test": "This is a JSONObject message payload"}, - message_contents["publish5"], - msg="Expect publish5 to be expected JSONObject") - self.assertEqual(["This is a JSONArray message payload"], - message_contents["publish6"], - msg="Expect publish6 to be expected JSONObject") + assert six.u("This is a string message payload") == message_contents["publish3"], "Expect publish3 to be expected String)" + assert b"This is a byte[] message payload" == message_contents["publish4"], "Expect publish4 to be expected byte[]. Actual: %s" % str(message_contents['publish4']) + assert {"test": "This is a JSONObject message payload"} == message_contents["publish5"], "Expect publish5 to be expected JSONObject" + assert ["This is a JSONArray message payload"] == message_contents["publish6"], "Expect publish6 to be expected JSONObject" def test_crypto_encrypted_unhandled(self): channel_name = self.get_channel_name('persisted:crypto_send_encrypted_unhandled') @@ -200,20 +176,20 @@ def test_crypto_encrypted_unhandled(self): history = rx_channel.history() message = history.items[0] cipher = get_cipher(get_default_params({'key': key})) - self.assertEqual(cipher.decrypt(message.data).decode(), data) - self.assertEqual(message.encoding, 'utf-8/cipher+aes-128-cbc') + assert cipher.decrypt(message.data).decode() == data + assert message.encoding == 'utf-8/cipher+aes-128-cbc' @dont_vary_protocol def test_cipher_params(self): params = CipherParams(secret_key='0123456789abcdef') - self.assertEqual(params.algorithm, 'AES') - self.assertEqual(params.mode, 'CBC') - self.assertEqual(params.key_length, 128) + assert params.algorithm == 'AES' + assert params.mode == 'CBC' + assert params.key_length == 128 params = CipherParams(secret_key='0123456789abcdef' * 2) - self.assertEqual(params.algorithm, 'AES') - self.assertEqual(params.mode, 'CBC') - self.assertEqual(params.key_length, 256) + assert params.algorithm == 'AES' + assert params.mode == 'CBC' + assert params.key_length == 256 class AbstractTestCryptoWithFixture(object): @@ -242,20 +218,20 @@ def get_encoded(self, encoded_item): # TM3 def test_decode(self): for item in self.items: - self.assertEqual(item['encoded']['name'], item['encrypted']['name']) + assert item['encoded']['name'] == item['encrypted']['name'] message = Message.from_encoded(item['encrypted'], self.cipher) - self.assertEqual(message.encoding, '') + assert message.encoding == '' expected_data = self.get_encoded(item['encoded']) - self.assertEqual(expected_data, message.data) + assert expected_data == message.data # TM3 def test_decode_array(self): items_encrypted = [item['encrypted'] for item in self.items] messages = Message.from_encoded_array(items_encrypted, self.cipher) for i, message in enumerate(messages): - self.assertEqual(message.encoding, '') + assert message.encoding == '' expected_data = self.get_encoded(self.items[i]['encoded']) - self.assertEqual(expected_data, message.data) + assert expected_data == message.data def test_encode(self): for item in self.items: @@ -267,8 +243,8 @@ def test_encode(self): message = Message(item['encoded']['name'], data) message.encrypt(self.cipher) as_dict = message.as_dict() - self.assertEqual(as_dict['data'], expected['data']) - self.assertEqual(as_dict['encoding'], expected['encoding']) + assert as_dict['data'] == expected['data'] + assert as_dict['encoding'] == expected['encoding'] class TestCryptoWithFixture128(AbstractTestCryptoWithFixture, BaseTestCase): From f4140ad2c210e22b2adb6987fd603a172d809027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 15:58:36 +0200 Subject: [PATCH 43/85] tests: use assert in restinit_test --- test/ably/restinit_test.py | 134 ++++++++++++++----------------------- 1 file changed, 50 insertions(+), 84 deletions(-) diff --git a/test/ably/restinit_test.py b/test/ably/restinit_test.py index c9cd0c28..6cae2626 100644 --- a/test/ably/restinit_test.py +++ b/test/ably/restinit_test.py @@ -1,8 +1,9 @@ from __future__ import absolute_import -import six from mock import patch +import pytest from requests import Session +import six from ably import AblyRest from ably import AblyException @@ -20,10 +21,8 @@ class TestRestInit(BaseTestCase): @dont_vary_protocol def test_key_only(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"]) - self.assertEqual(ably.options.key_name, test_vars["keys"][0]["key_name"], - "Key name does not match") - self.assertEqual(ably.options.key_secret, test_vars["keys"][0]["key_secret"], - "Key secret does not match") + assert ably.options.key_name == test_vars["keys"][0]["key_name"], "Key name does not match" + assert ably.options.key_secret == test_vars["keys"][0]["key_secret"], "Key secret does not match" def per_protocol_setup(self, use_binary_protocol): self.use_binary_protocol = use_binary_protocol @@ -31,14 +30,13 @@ def per_protocol_setup(self, use_binary_protocol): @dont_vary_protocol def test_with_token(self): ably = AblyRest(token="foo") - self.assertEqual(ably.options.auth_token, "foo", - "Token not set at options") + assert ably.options.auth_token == "foo", "Token not set at options" @dont_vary_protocol def test_with_token_details(self): td = TokenDetails() ably = AblyRest(token_details=td) - self.assertIs(ably.options.token_details, td) + assert ably.options.token_details is td @dont_vary_protocol def test_with_options_token_callback(self): @@ -48,27 +46,23 @@ def token_callback(**params): @dont_vary_protocol def test_ambiguous_key_raises_value_error(self): - self.assertRaisesRegexp(ValueError, "mutually exclusive", AblyRest, - key=test_vars["keys"][0]["key_str"], - key_name='x') - self.assertRaisesRegexp(ValueError, "mutually exclusive", AblyRest, - key=test_vars["keys"][0]["key_str"], - key_secret='x') + with pytest.raises(ValueError, match="mutually exclusive"): + AblyRest(key=test_vars["keys"][0]["key_str"], key_name='x') + with pytest.raises(ValueError, match="mutually exclusive"): + AblyRest(key=test_vars["keys"][0]["key_str"], key_secret='x') @dont_vary_protocol def test_with_key_name_or_secret_only(self): - self.assertRaisesRegexp(ValueError, "key is missing", AblyRest, - key_name='x') - self.assertRaisesRegexp(ValueError, "key is missing", AblyRest, - key_secret='x') + with pytest.raises(ValueError, match="key is missing"): + AblyRest(key_name='x') + with pytest.raises(ValueError, match="key is missing"): + AblyRest(key_secret='x') @dont_vary_protocol def test_with_key_name_and_secret(self): ably = AblyRest(key_name="foo", key_secret="bar") - self.assertEqual(ably.options.key_name, "foo", - "Key name does not match") - self.assertEqual(ably.options.key_secret, "bar", - "Key secret does not match") + assert ably.options.key_name == "foo", "Key name does not match" + assert ably.options.key_secret == "bar", "Key secret does not match" @dont_vary_protocol def test_with_options_auth_url(self): @@ -79,23 +73,20 @@ def test_with_options_auth_url(self): def test_rest_host_and_environment(self): # rest host ably = AblyRest(token='foo', rest_host="some.other.host") - self.assertEqual("some.other.host", ably.options.rest_host, - msg="Unexpected host mismatch") + assert "some.other.host" == ably.options.rest_host, "Unexpected host mismatch" # environment: production ably = AblyRest(token='foo', environment="production") host = ably.options.get_rest_host() - self.assertEqual("rest.ably.io", host, - msg="Unexpected host mismatch %s" % host) + assert "rest.ably.io" == host, "Unexpected host mismatch %s" % host # environment: other ably = AblyRest(token='foo', environment="sandbox") host = ably.options.get_rest_host() - self.assertEqual("sandbox-rest.ably.io", host, - msg="Unexpected host mismatch %s" % host) + assert "sandbox-rest.ably.io" == host, "Unexpected host mismatch %s" % host # both, as per #TO3k2 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ably = AblyRest(token='foo', rest_host="some.other.host", environment="some.other.environment") @@ -110,77 +101,59 @@ def test_fallback_hosts(self): for aux in fallback_hosts: ably = AblyRest(token='foo', fallback_hosts=aux) - self.assertEqual( - sorted(aux), - sorted(ably.options.get_fallback_rest_hosts()) - ) + assert sorted(aux) == sorted(ably.options.get_fallback_rest_hosts()) # Specify environment ably = AblyRest(token='foo', environment='sandbox') - self.assertEqual( - [], - sorted(ably.options.get_fallback_rest_hosts()) - ) + assert [] == sorted(ably.options.get_fallback_rest_hosts()) # Specify environment and fallback_hosts_use_default # We specify http_max_retry_count=10 so all the fallback hosts get in the list ably = AblyRest(token='foo', environment='sandbox', fallback_hosts_use_default=True, http_max_retry_count=10) - self.assertEqual( - sorted(Defaults.fallback_hosts), - sorted(ably.options.get_fallback_rest_hosts()) - ) + assert sorted(Defaults.fallback_hosts) == sorted(ably.options.get_fallback_rest_hosts()) @dont_vary_protocol def test_specified_realtime_host(self): ably = AblyRest(token='foo', realtime_host="some.other.host") - self.assertEqual("some.other.host", ably.options.realtime_host, - msg="Unexpected host mismatch") + assert "some.other.host" == ably.options.realtime_host, "Unexpected host mismatch" @dont_vary_protocol def test_specified_port(self): ably = AblyRest(token='foo', port=9998, tls_port=9999) - self.assertEqual(9999, Defaults.get_port(ably.options), - msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % - ably.options.tls_port) + assert 9999 == Defaults.get_port(ably.options), "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port @dont_vary_protocol def test_specified_non_tls_port(self): ably = AblyRest(token='foo', port=9998, tls=False) - self.assertEqual(9998, Defaults.get_port(ably.options), - msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % - ably.options.tls_port) + assert 9998 == Defaults.get_port(ably.options), "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port @dont_vary_protocol def test_specified_tls_port(self): ably = AblyRest(token='foo', tls_port=9999, tls=True) - self.assertEqual(9999, Defaults.get_port(ably.options), - msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % - ably.options.tls_port) + assert 9999 == Defaults.get_port(ably.options), "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port @dont_vary_protocol def test_tls_defaults_to_true(self): ably = AblyRest(token='foo') - self.assertTrue(ably.options.tls, - msg="Expected encryption to default to true") - self.assertEqual(Defaults.tls_port, Defaults.get_port(ably.options), - msg="Unexpected port mismatch") + assert ably.options.tls, "Expected encryption to default to true" + assert Defaults.tls_port == Defaults.get_port(ably.options), "Unexpected port mismatch" @dont_vary_protocol def test_tls_can_be_disabled(self): ably = AblyRest(token='foo', tls=False) - self.assertFalse(ably.options.tls, - msg="Expected encryption to be False") - self.assertEqual(Defaults.port, Defaults.get_port(ably.options), - msg="Unexpected port mismatch") + assert not ably.options.tls, "Expected encryption to be False" + assert Defaults.port == Defaults.get_port(ably.options), "Unexpected port mismatch" @dont_vary_protocol def test_with_no_params(self): - self.assertRaises(ValueError, AblyRest) + with pytest.raises(ValueError): + AblyRest() @dont_vary_protocol def test_with_no_auth_params(self): - self.assertRaises(ValueError, AblyRest, port=111) + with pytest.raises(ValueError): + AblyRest(port=111) def test_query_time_param(self): ably = AblyRest(key=test_vars["keys"][0]["key_str"], @@ -194,38 +167,31 @@ def test_query_time_param(self): with patch('ably.rest.rest.AblyRest.time', wraps=ably.time) as server_time,\ patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: ably.auth.request_token() - self.assertFalse(local_time.called) - self.assertTrue(server_time.called) + assert not local_time.called + assert server_time.called @dont_vary_protocol def test_requests_over_https_production(self): ably = AblyRest(token='token') - self.assertEquals('https://rest.ably.io', - '{0}://{1}'.format( - ably.http.preferred_scheme, - ably.http.preferred_host)) - self.assertEqual(ably.http.preferred_port, 443) + assert 'https://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host) + assert ably.http.preferred_port == 443 @dont_vary_protocol def test_requests_over_http_production(self): ably = AblyRest(token='token', tls=False) - self.assertEquals('http://rest.ably.io', - '{0}://{1}'.format( - ably.http.preferred_scheme, - ably.http.preferred_host)) - self.assertEqual(ably.http.preferred_port, 80) + assert 'http://rest.ably.io' == '{0}://{1}'.format(ably.http.preferred_scheme, ably.http.preferred_host) + assert ably.http.preferred_port == 80 @dont_vary_protocol def test_request_basic_auth_over_http_fails(self): ably = AblyRest(key_secret='foo', key_name='bar', tls=False) - with self.assertRaises(AblyException) as cm: + with pytest.raises(AblyException) as excinfo: ably.http.get('/time', skip_auth=False) - self.assertEqual(401, cm.exception.status_code) - self.assertEqual(40103, cm.exception.code) - self.assertEqual('Cannot use Basic Auth over non-TLS connections', - cm.exception.message) + assert 401 == excinfo.value.status_code + assert 40103 == excinfo.value.code + assert 'Cannot use Basic Auth over non-TLS connections' == excinfo.value.message @dont_vary_protocol def test_enviroment(self): @@ -237,7 +203,7 @@ def test_enviroment(self): except AblyException: pass request = get_mock.call_args_list[0][0][0] - self.assertEquals(request.url, 'https://custom-rest.ably.io:443/time') + assert request.url == 'https://custom-rest.ably.io:443/time' @dont_vary_protocol def test_accepts_custom_http_timeouts(self): @@ -245,7 +211,7 @@ def test_accepts_custom_http_timeouts(self): token="foo", http_request_timeout=30, http_open_timeout=8, http_max_retry_count=6, http_max_retry_duration=20) - self.assertEqual(ably.options.http_request_timeout, 30) - self.assertEqual(ably.options.http_open_timeout, 8) - self.assertEqual(ably.options.http_max_retry_count, 6) - self.assertEqual(ably.options.http_max_retry_duration, 20) + assert ably.options.http_request_timeout == 30 + assert ably.options.http_open_timeout == 8 + assert ably.options.http_max_retry_count == 6 + assert ably.options.http_max_retry_duration == 20 From 421b02f53890a8521eef7c4634b689f79878cd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 16:14:08 +0200 Subject: [PATCH 44/85] tests: use assert in restappstats_test --- test/ably/restappstats_test.py | 91 ++++++++++++++++------------------ 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/test/ably/restappstats_test.py b/test/ably/restappstats_test.py index 5c49baa0..10ba79a6 100644 --- a/test/ably/restappstats_test.py +++ b/test/ably/restappstats_test.py @@ -5,6 +5,7 @@ from datetime import timedelta import logging +import pytest import six from ably import AblyRest @@ -111,12 +112,12 @@ def get_params(cls): } def test_stats_are_forward(self): - self.assertEqual(self.stat.inbound.realtime.all.count, 50) + assert self.stat.inbound.realtime.all.count == 50 def test_three_pages(self): - self.assertFalse(self.stats_pages.is_last()) + assert not self.stats_pages.is_last() page3 = self.stats_pages.next().next() - self.assertEqual(page3.items[0].inbound.realtime.all.count, 70) + assert page3.items[0].inbound.realtime.all.count == 70 @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -132,12 +133,12 @@ def get_params(cls): } def test_stats_are_forward(self): - self.assertEqual(self.stat.inbound.realtime.all.count, 70) + assert self.stat.inbound.realtime.all.count == 70 def test_three_pages(self): - self.assertFalse(self.stats_pages.is_last()) + assert not self.stats_pages.is_last() page3 = self.stats_pages.next().next() - self.assertEqual(page3.items[0].inbound.realtime.all.count, 50) + assert page3.items[0].inbound.realtime.all.count == 50 @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -152,8 +153,8 @@ def get_params(cls): } def test_default_is_backwards(self): - self.assertEqual(self.stats[0].inbound.realtime.messages.count, 70) - self.assertEqual(self.stats[-1].inbound.realtime.messages.count, 50) + assert self.stats[0].inbound.realtime.messages.count == 70 + assert self.stats[-1].inbound.realtime.messages.count == 50 @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -167,9 +168,9 @@ def get_params(cls): } def test_default_100_pagination(self): - self.assertEqual(len(self.stats), 100) + assert len(self.stats) == 100 next_page = self.stats_pages.next().items - self.assertEqual(len(next_page), 20) + assert len(next_page) == 20 @six.add_metaclass(VaryByProtocolTestsMetaclass) @@ -179,12 +180,11 @@ class TestRestAppStats(TestRestAppStatsSetup, BaseTestCase): def test_protocols(self): self.stats_pages = self.ably.stats(**self.get_params()) self.stats_pages1 = self.ably_text.stats(**self.get_params()) - self.assertEqual(len(self.stats_pages.items), - len(self.stats_pages1.items)) + assert len(self.stats_pages.items) == len(self.stats_pages1.items) def test_paginated_response(self): - self.assertIsInstance(self.stats_pages, PaginatedResult) - self.assertIsInstance(self.stats_pages.items[0], Stats) + assert isinstance(self.stats_pages, PaginatedResult) + assert isinstance(self.stats_pages.items[0], Stats) def test_units(self): for unit in ['hour', 'day', 'month']: @@ -197,11 +197,9 @@ def test_units(self): } stats_pages = self.ably.stats(**params) stat = stats_pages.items[0] - self.assertEquals(len(stats_pages.items), 1) - self.assertEqual(stat.all.messages.count, - 50 + 20 + 60 + 10 + 70 + 40) - self.assertEqual(stat.all.messages.data, - 5000 + 2000 + 6000 + 1000 + 7000 + 4000) + assert len(stats_pages.items) == 1 + assert stat.all.messages.count == 50 + 20 + 60 + 10 + 70 + 40 + assert stat.all.messages.data == 5000 + 2000 + 6000 + 1000 + 7000 + 4000 @dont_vary_protocol def test_when_argument_start_is_after_end(self): @@ -210,7 +208,7 @@ def test_when_argument_start_is_after_end(self): 'end': self.last_interval - timedelta(minutes=2), 'unit': 'minute', } - with self.assertRaisesRegexp(AblyException, "'end' parameter has to be greater than or equal to 'start'"): + with pytest.raises(AblyException, match="'end' parameter has to be greater than or equal to 'start'"): self.ably.stats(**params) @dont_vary_protocol @@ -219,7 +217,7 @@ def test_when_limit_gt_1000(self): 'end': self.last_interval, 'limit': 5000 } - with self.assertRaisesRegexp(AblyException, "The maximum allowed limit is 1000"): + with pytest.raises(AblyException, match="The maximum allowed limit is 1000"): self.ably.stats(**params) def test_no_arguments(self): @@ -228,63 +226,62 @@ def test_no_arguments(self): } self.stats_pages = self.ably.stats(**params) self.stat = self.stats_pages.items[0] - self.assertEquals(self.stat.interval_granularity, 'minute') + assert self.stat.interval_granularity == 'minute' def test_got_1_record(self): - self.assertEqual(1, len(self.stats_pages.items), "Expected 1 record") + assert 1 == len(self.stats_pages.items), "Expected 1 record" def test_zero_by_default(self): - self.assertEqual(self.stat.channels.refused, 0) - self.assertEqual(self.stat.outbound.webhook.all.count, 0) + assert self.stat.channels.refused == 0 + assert self.stat.outbound.webhook.all.count == 0 def test_return_aggregated_message_data(self): # returns aggregated message data - self.assertEqual(self.stat.all.messages.count, 70 + 40) - self.assertEqual(self.stat.all.messages.data, 7000 + 4000) + assert self.stat.all.messages.count == 70 + 40 + assert self.stat.all.messages.data == 7000 + 4000 def test_inbound_realtime_all_data(self): # returns inbound realtime all data - self.assertEqual(self.stat.inbound.realtime.all.count, 70) - self.assertEqual(self.stat.inbound.realtime.all.data, 7000) + assert self.stat.inbound.realtime.all.count == 70 + assert self.stat.inbound.realtime.all.data == 7000 def test_inboud_realtime_message_data(self): # returns inbound realtime message data - self.assertEqual(self.stat.inbound.realtime.messages.count, 70) - self.assertEqual(self.stat.inbound.realtime.messages.data, 7000) + assert self.stat.inbound.realtime.messages.count == 70 + assert self.stat.inbound.realtime.messages.data == 7000 def test_outbound_realtime_all_data(self): # returns outboud realtime all data - self.assertEqual(self.stat.outbound.realtime.all.count, 40) - self.assertEqual(self.stat.outbound.realtime.all.data, 4000) + assert self.stat.outbound.realtime.all.count == 40 + assert self.stat.outbound.realtime.all.data == 4000 def test_persisted_data(self): # returns persisted presence all data - self.assertEqual(self.stat.persisted.all.count, 20) - self.assertEqual(self.stat.persisted.all.data, 2000) + assert self.stat.persisted.all.count == 20 + assert self.stat.persisted.all.data == 2000 def test_connections_data(self): # returns connections all data - self.assertEqual(self.stat.connections.tls.peak, 20) - self.assertEqual(self.stat.connections.tls.opened, 10) + assert self.stat.connections.tls.peak == 20 + assert self.stat.connections.tls.opened == 10 def test_channels_all_data(self): # returns channels all data - self.assertEqual(self.stat.channels.peak, 50) - self.assertEqual(self.stat.channels.opened, 30) + assert self.stat.channels.peak == 50 + assert self.stat.channels.opened == 30 def test_api_requests_data(self): # returns api_requests data - self.assertEqual(self.stat.api_requests.succeeded, 50) - self.assertEqual(self.stat.api_requests.failed, 10) + assert self.stat.api_requests.succeeded == 50 + assert self.stat.api_requests.failed == 10 def test_token_requests(self): # returns token_requests data - self.assertEqual(self.stat.token_requests.succeeded, 60) - self.assertEqual(self.stat.token_requests.failed, 20) + assert self.stat.token_requests.succeeded == 60 + assert self.stat.token_requests.failed == 20 def test_inverval(self): # interval - self.assertEqual(self.stat.interval_granularity, 'minute') - self.assertEqual(self.stat.interval_id, - self.last_interval.strftime('%Y-%m-%d:%H:%M')) - self.assertEqual(self.stat.interval_time, self.last_interval) + assert self.stat.interval_granularity == 'minute' + assert self.stat.interval_id == self.last_interval.strftime('%Y-%m-%d:%H:%M') + assert self.stat.interval_time == self.last_interval From 1398dd0f7d71489d46dcebb0716d4490a4564569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 16:44:10 +0200 Subject: [PATCH 45/85] tests: use assert in resttoken_test --- test/ably/resttoken_test.py | 170 ++++++++++++++---------------------- 1 file changed, 66 insertions(+), 104 deletions(-) diff --git a/test/ably/resttoken_test.py b/test/ably/resttoken_test.py index da269a58..07d5671e 100644 --- a/test/ably/resttoken_test.py +++ b/test/ably/resttoken_test.py @@ -5,6 +5,7 @@ import logging from mock import patch +import pytest import six from ably import AblyException @@ -43,53 +44,35 @@ def test_request_token_null_params(self): pre_time = self.server_time() token_details = self.ably.auth.request_token() post_time = self.server_time() - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertGreaterEqual(token_details.issued, - pre_time, - msg="Unexpected issued time") - self.assertLessEqual(token_details.issued, - post_time, - msg="Unexpected issued time") - self.assertEqual(self.permit_all, - six.text_type(token_details.capability), - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert token_details.issued >= pre_time, "Unexpected issued time" + assert token_details.issued <= post_time, "Unexpected issued time" + assert self.permit_all == six.text_type(token_details.capability), "Unexpected capability" def test_request_token_explicit_timestamp(self): pre_time = self.server_time() token_details = self.ably.auth.request_token(token_params={'timestamp': pre_time}) post_time = self.server_time() - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertGreaterEqual(token_details.issued, - pre_time, - msg="Unexpected issued time") - self.assertLessEqual(token_details.issued, - post_time, - msg="Unexpected issued time") - self.assertEqual(self.permit_all, - six.text_type(Capability(token_details.capability)), - msg="Unexpected Capability") + assert token_details.token is not None, "Expected token" + assert token_details.issued >= pre_time, "Unexpected issued time" + assert token_details.issued <= post_time, "Unexpected issued time" + assert self.permit_all == six.text_type(Capability(token_details.capability)), "Unexpected Capability" def test_request_token_explicit_invalid_timestamp(self): request_time = self.server_time() explicit_timestamp = request_time - 30 * 60 * 1000 - self.assertRaises(AblyException, self.ably.auth.request_token, - token_params={'timestamp': explicit_timestamp}) + with pytest.raises(AblyException): + self.ably.auth.request_token(token_params={'timestamp': explicit_timestamp}) def test_request_token_with_system_timestamp(self): pre_time = self.server_time() token_details = self.ably.auth.request_token(query_time=True) post_time = self.server_time() - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertGreaterEqual(token_details.issued, - pre_time, - msg="Unexpected issued time") - self.assertLessEqual(token_details.issued, - post_time, - msg="Unexpected issued time") - self.assertEqual(self.permit_all, - six.text_type(Capability(token_details.capability)), - msg="Unexpected Capability") + assert token_details.token is not None, "Expected token" + assert token_details.issued >= pre_time, "Unexpected issued time" + assert token_details.issued <= post_time, "Unexpected issued time" + assert self.permit_all == six.text_type(Capability(token_details.capability)), "Unexpected Capability" def test_request_token_with_duplicate_nonce(self): request_time = self.server_time() @@ -97,12 +80,11 @@ def test_request_token_with_duplicate_nonce(self): 'timestamp': request_time, 'nonce': '1234567890123456' } - token_details = self.ably.auth.request_token( - token_params) - self.assertIsNotNone(token_details.token, msg="Expected token") + token_details = self.ably.auth.request_token(token_params) + assert token_details.token is not None, "Expected token" - self.assertRaises(AblyException, self.ably.auth.request_token, - token_params) + with pytest.raises(AblyException): + self.ably.auth.request_token(token_params) def test_request_token_with_capability_that_subsets_key_capability(self): capability = Capability({ @@ -112,57 +94,53 @@ def test_request_token_with_capability_that_subsets_key_capability(self): token_details = self.ably.auth.request_token( token_params={'capability': capability}) - self.assertIsNotNone(token_details) - self.assertIsNotNone(token_details.token) - self.assertEqual(capability, token_details.capability, - msg="Unexpected capability") + assert token_details is not None + assert token_details.token is not None + assert capability == token_details.capability, "Unexpected capability" def test_request_token_with_specified_key(self): key = RestSetup.get_test_vars()["keys"][1] token_details = self.ably.auth.request_token( key_name=key["key_name"], key_secret=key["key_secret"]) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(key.get("capability"), - token_details.capability, - msg="Unexpected capability") + assert token_details.token is not None, "Expected token" + assert key.get("capability") == token_details.capability, "Unexpected capability" @dont_vary_protocol def test_request_token_with_invalid_mac(self): - self.assertRaises(AblyException, self.ably.auth.request_token, - token_params={'mac': "thisisnotavalidmac"}) + with pytest.raises(AblyException): + self.ably.auth.request_token(token_params={'mac': "thisisnotavalidmac"}) def test_request_token_with_specified_ttl(self): token_details = self.ably.auth.request_token(token_params={'ttl': 100}) - self.assertIsNotNone(token_details.token, msg="Expected token") - self.assertEqual(token_details.issued + 100, - token_details.expires, msg="Unexpected expires") + assert token_details.token is not None, "Expected token" + assert token_details.issued + 100 == token_details.expires, "Unexpected expires" @dont_vary_protocol def test_token_with_excessive_ttl(self): excessive_ttl = 365 * 24 * 60 * 60 * 1000 - self.assertRaises(AblyException, self.ably.auth.request_token, - token_params={'ttl': excessive_ttl}) + with pytest.raises(AblyException): + self.ably.auth.request_token(token_params={'ttl': excessive_ttl}) @dont_vary_protocol def test_token_generation_with_invalid_ttl(self): - self.assertRaises(AblyException, self.ably.auth.request_token, - token_params={'ttl': -1}) + with pytest.raises(AblyException): + self.ably.auth.request_token(token_params={'ttl': -1}) def test_token_generation_with_local_time(self): timestamp = self.ably.auth._timestamp with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: self.ably.auth.request_token() - self.assertTrue(local_time.called) - self.assertFalse(server_time.called) + assert local_time.called + assert not server_time.called def test_token_generation_with_server_time(self): timestamp = self.ably.auth._timestamp with patch('ably.rest.rest.AblyRest.time', wraps=self.ably.time) as server_time,\ patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: self.ably.auth.request_token(query_time=True) - self.assertFalse(local_time.called) - self.assertTrue(server_time.called) + assert not local_time.called + assert server_time.called # TD7 def test_toke_details_from_json(self): @@ -170,15 +148,8 @@ def test_toke_details_from_json(self): token_details_dict = token_details.to_dict() token_details_str = json.dumps(token_details_dict) - self.assertEqual( - token_details, - TokenDetails.from_json(token_details_dict), - ) - - self.assertEqual( - token_details, - TokenDetails.from_json(token_details_str), - ) + assert token_details == TokenDetails.from_json(token_details_dict) + assert token_details == TokenDetails.from_json(token_details_str) # Issue #71 @dont_vary_protocol @@ -211,14 +182,12 @@ def test_key_name_and_secret_are_required(self): port=test_vars["port"], tls_port=test_vars["tls_port"], tls=test_vars["tls"]) - self.assertRaisesRegexp(AblyException, "40101 401 No key specified", - ably.auth.create_token_request) - self.assertRaisesRegexp(AblyException, "40101 401 No key specified", - ably.auth.create_token_request, - key_name=self.key_name) - self.assertRaisesRegexp(AblyException, "40101 401 No key specified", - ably.auth.create_token_request, - key_secret=self.key_secret) + with pytest.raises(AblyException, match="40101 401 No key specified"): + ably.auth.create_token_request() + with pytest.raises(AblyException, match="40101 401 No key specified"): + ably.auth.create_token_request(key_name=self.key_name) + with pytest.raises(AblyException, match="40101 401 No key specified"): + ably.auth.create_token_request(key_secret=self.key_secret) @dont_vary_protocol def test_with_local_time(self): @@ -227,8 +196,8 @@ def test_with_local_time(self): patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, query_time=False) - self.assertTrue(local_time.called) - self.assertFalse(server_time.called) + assert local_time.called + assert not server_time.called @dont_vary_protocol def test_with_server_time(self): @@ -237,13 +206,13 @@ def test_with_server_time(self): patch('ably.rest.auth.Auth._timestamp', wraps=timestamp) as local_time: self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, query_time=True) - self.assertTrue(server_time.called) - self.assertFalse(local_time.called) + assert server_time.called + assert not local_time.called def test_token_request_can_be_used_to_get_a_token(self): token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertIsInstance(token_request, TokenRequest) + assert isinstance(token_request, TokenRequest) def auth_callback(token_params): return token_request @@ -257,52 +226,46 @@ def auth_callback(token_params): token = ably.auth.authorize() - self.assertIsInstance(token, TokenDetails) + assert isinstance(token, TokenDetails) # TE6 @dont_vary_protocol def test_token_request_from_json(self): token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertIsInstance(token_request, TokenRequest) + assert isinstance(token_request, TokenRequest) token_request_dict = token_request.to_dict() - self.assertEqual( - token_request, - TokenRequest.from_json(token_request_dict), - ) + assert token_request == TokenRequest.from_json(token_request_dict) token_request_str = json.dumps(token_request_dict) - self.assertEqual( - token_request, - TokenRequest.from_json(token_request_str), - ) + assert token_request == TokenRequest.from_json(token_request_str) @dont_vary_protocol def test_nonce_is_random_and_longer_than_15_characters(self): token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertGreater(len(token_request.nonce), 15) + assert len(token_request.nonce) > 15 another_token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertGreater(len(another_token_request.nonce), 15) + assert len(another_token_request.nonce) > 15 - self.assertNotEqual(token_request.nonce, another_token_request.nonce) + assert token_request.nonce != another_token_request.nonce # RSA5 @dont_vary_protocol def test_ttl_is_optional_and_specified_in_ms(self): token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertEquals(token_request.ttl, None) + assert token_request.ttl is None # RSA6 @dont_vary_protocol def test_capability_is_optional(self): token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret) - self.assertEquals(token_request.capability, None) + assert token_request.capability is None @dont_vary_protocol def test_accept_all_token_params(self): @@ -317,18 +280,18 @@ def test_accept_all_token_params(self): token_params, key_name=self.key_name, key_secret=self.key_secret, ) - self.assertEqual(token_request.ttl, token_params['ttl']) - self.assertEqual(token_request.capability, str(token_params['capability'])) - self.assertEqual(token_request.client_id, token_params['client_id']) - self.assertEqual(token_request.timestamp, token_params['timestamp']) - self.assertEqual(token_request.nonce, token_params['nonce']) + assert token_request.ttl == token_params['ttl'] + assert token_request.capability == str(token_params['capability']) + assert token_request.client_id == token_params['client_id'] + assert token_request.timestamp == token_params['timestamp'] + assert token_request.nonce == token_params['nonce'] def test_capability(self): capability = Capability({'channel': ['publish']}) token_request = self.ably.auth.create_token_request( key_name=self.key_name, key_secret=self.key_secret, token_params={'capability': capability}) - self.assertEqual(token_request.capability, str(capability)) + assert token_request.capability == str(capability) def auth_callback(token_params): return token_request @@ -342,7 +305,7 @@ def auth_callback(token_params): token = ably.auth.authorize() - self.assertEqual(str(token.capability), str(capability)) + assert str(token.capability) == str(capability) @dont_vary_protocol def test_hmac(self): @@ -355,5 +318,4 @@ def test_hmac(self): } token_request = ably.auth.create_token_request( token_params, key_secret='a_secret', key_name='a_key_name') - self.assertEqual( - token_request.mac, 'sYkCH0Un+WgzI7/Nhy0BoQIKq9HmjKynCRs4E3qAbGQ=') + assert token_request.mac == 'sYkCH0Un+WgzI7/Nhy0BoQIKq9HmjKynCRs4E3qAbGQ=' From 228610969abf106a0f562c519497dbf39a4f3371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 2 Aug 2018 16:53:56 +0200 Subject: [PATCH 46/85] tests: use assert in encoders_test The last one --- test/ably/encoders_test.py | 184 +++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 100 deletions(-) diff --git a/test/ably/encoders_test.py b/test/ably/encoders_test.py index 87102d2c..38875c15 100644 --- a/test/ably/encoders_test.py +++ b/test/ably/encoders_test.py @@ -36,8 +36,8 @@ def test_text_utf8(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', six.u('foó')) _, kwargs = post_mock.call_args - self.assertEqual(json.loads(kwargs['body'])['data'], six.u('foó')) - self.assertFalse(json.loads(kwargs['body']).get('encoding', '')) + assert json.loads(kwargs['body'])['data'] == six.u('foó') + assert not json.loads(kwargs['body']).get('encoding', '') def test_str(self): # This test only makes sense for py2 @@ -46,8 +46,8 @@ def test_str(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', 'foo') _, kwargs = post_mock.call_args - self.assertEqual(json.loads(kwargs['body'])['data'], 'foo') - self.assertFalse(json.loads(kwargs['body']).get('encoding', '')) + assert json.loads(kwargs['body'])['data'] == 'foo' + assert not json.loads(kwargs['body']).get('encoding', '') def test_with_binary_type(self): channel = self.ably.channels["persisted:publish"] @@ -56,10 +56,8 @@ def test_with_binary_type(self): channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args raw_data = json.loads(kwargs['body'])['data'] - self.assertEqual(base64.b64decode(raw_data.encode('ascii')), - bytearray(b'foo')) - self.assertEqual(json.loads(kwargs['body'])['encoding'].strip('/'), - 'base64') + assert base64.b64decode(raw_data.encode('ascii')) == bytearray(b'foo') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'base64' def test_with_bytes_type(self): # this test is only relevant for python3 @@ -70,10 +68,8 @@ def test_with_bytes_type(self): channel.publish('event', b'foo') _, kwargs = post_mock.call_args raw_data = json.loads(kwargs['body'])['data'] - self.assertEqual(base64.b64decode(raw_data.encode('ascii')), - bytearray(b'foo')) - self.assertEqual(json.loads(kwargs['body'])['encoding'].strip('/'), - 'base64') + assert base64.b64decode(raw_data.encode('ascii')) == bytearray(b'foo') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'base64' def test_with_json_dict_data(self): channel = self.ably.channels["persisted:publish"] @@ -82,9 +78,8 @@ def test_with_json_dict_data(self): channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(json.loads(kwargs['body'])['data']) - self.assertEqual(raw_data, data) - self.assertEqual(json.loads(kwargs['body'])['encoding'].strip('/'), - 'json') + assert raw_data == data + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json' def test_with_json_list_data(self): channel = self.ably.channels["persisted:publish"] @@ -93,59 +88,58 @@ def test_with_json_list_data(self): channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(json.loads(kwargs['body'])['data']) - self.assertEqual(raw_data, data) - self.assertEqual(json.loads(kwargs['body'])['encoding'].strip('/'), - 'json') + assert raw_data == data + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json' def test_text_utf8_decode(self): channel = self.ably.channels["persisted:stringdecode"] channel.publish('event', six.u('fóo')) message = channel.history().items[0] - self.assertEqual(message.data, six.u('fóo')) - self.assertIsInstance(message.data, six.text_type) - self.assertFalse(message.encoding) + assert message.data == six.u('fóo') + assert isinstance(message.data, six.text_type) + assert not message.encoding def test_text_str_decode(self): channel = self.ably.channels["persisted:stringnonutf8decode"] channel.publish('event', 'foo') message = channel.history().items[0] - self.assertEqual(message.data, six.u('foo')) - self.assertIsInstance(message.data, six.text_type) - self.assertFalse(message.encoding) + assert message.data == six.u('foo') + assert isinstance(message.data, six.text_type) + assert not message.encoding def test_with_binary_type_decode(self): channel = self.ably.channels["persisted:binarydecode"] channel.publish('event', bytearray(b'foob')) message = channel.history().items[0] - self.assertEqual(message.data, bytearray(b'foob')) - self.assertIsInstance(message.data, bytearray) - self.assertFalse(message.encoding) + assert message.data == bytearray(b'foob') + assert isinstance(message.data, bytearray) + assert not message.encoding def test_with_json_dict_data_decode(self): channel = self.ably.channels["persisted:jsondict"] data = {six.u('foó'): six.u('bár')} channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding def test_with_json_list_data_decode(self): channel = self.ably.channels["persisted:jsonarray"] data = [six.u('foó'), six.u('bár')] channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding def test_decode_with_invalid_encoding(self): data = six.u('foó') encoded = base64.b64encode(data.encode('utf-8')) decoded_data = Message.decode(encoded, 'foo/bar/utf-8/base64') - self.assertEqual(decoded_data['data'], data) - self.assertEqual(decoded_data['encoding'], 'foo/bar') + assert decoded_data['data'] == data + assert decoded_data['encoding'] == 'foo/bar' class TestTextEncodersEncryption(BaseTestCase): @@ -171,10 +165,9 @@ def test_text_utf8(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', six.u('fóo')) _, kwargs = post_mock.call_args - self.assertEquals(json.loads(kwargs['body'])['encoding'].strip('/'), - 'utf-8/cipher+aes-128-cbc/base64') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'utf-8/cipher+aes-128-cbc/base64' data = self.decrypt(json.loads(kwargs['body'])['data']).decode('utf-8') - self.assertEquals(data, six.u('fóo')) + assert data == six.u('fóo') def test_str(self): # This test only makes sense for py2 @@ -183,8 +176,8 @@ def test_str(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', 'foo') _, kwargs = post_mock.call_args - self.assertEqual(json.loads(kwargs['body'])['data'], 'foo') - self.assertFalse(json.loads(kwargs['body']).get('encoding', '')) + assert json.loads(kwargs['body'])['data'] == 'foo' + assert not json.loads(kwargs['body']).get('encoding', '') def test_with_binary_type(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -194,11 +187,10 @@ def test_with_binary_type(self): channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args - self.assertEquals(json.loads(kwargs['body'])['encoding'].strip('/'), - 'cipher+aes-128-cbc/base64') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'cipher+aes-128-cbc/base64' data = self.decrypt(json.loads(kwargs['body'])['data']) - self.assertEqual(data, bytearray(b'foo')) - self.assertIsInstance(data, bytearray) + assert data == bytearray(b'foo') + assert isinstance(data, bytearray) def test_with_json_dict_data(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -207,10 +199,9 @@ def test_with_json_dict_data(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', data) _, kwargs = post_mock.call_args - self.assertEquals(json.loads(kwargs['body'])['encoding'].strip('/'), - 'json/utf-8/cipher+aes-128-cbc/base64') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc/base64' raw_data = self.decrypt(json.loads(kwargs['body'])['data']).decode('ascii') - self.assertEqual(json.loads(raw_data), data) + assert json.loads(raw_data) == data def test_with_json_list_data(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -219,19 +210,18 @@ def test_with_json_list_data(self): with mock.patch('ably.rest.rest.Http.post') as post_mock: channel.publish('event', data) _, kwargs = post_mock.call_args - self.assertEquals(json.loads(kwargs['body'])['encoding'].strip('/'), - 'json/utf-8/cipher+aes-128-cbc/base64') + assert json.loads(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc/base64' raw_data = self.decrypt(json.loads(kwargs['body'])['data']).decode('ascii') - self.assertEqual(json.loads(raw_data), data) + assert json.loads(raw_data) == data def test_text_utf8_decode(self): channel = self.ably.channels.get("persisted:enc_stringdecode", cipher=self.cipher_params) channel.publish('event', six.u('foó')) message = channel.history().items[0] - self.assertEqual(message.data, six.u('foó')) - self.assertIsInstance(message.data, six.text_type) - self.assertFalse(message.encoding) + assert message.data == six.u('foó') + assert isinstance(message.data, six.text_type) + assert not message.encoding def test_with_binary_type_decode(self): channel = self.ably.channels.get("persisted:enc_binarydecode", @@ -239,9 +229,9 @@ def test_with_binary_type_decode(self): channel.publish('event', bytearray(b'foob')) message = channel.history().items[0] - self.assertEqual(message.data, bytearray(b'foob')) - self.assertIsInstance(message.data, bytearray) - self.assertFalse(message.encoding) + assert message.data == bytearray(b'foob') + assert isinstance(message.data, bytearray) + assert not message.encoding def test_with_json_dict_data_decode(self): channel = self.ably.channels.get("persisted:enc_jsondict", @@ -249,8 +239,8 @@ def test_with_json_dict_data_decode(self): data = {six.u('foó'): six.u('bár')} channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding def test_with_json_list_data_decode(self): channel = self.ably.channels.get("persisted:enc_list", @@ -258,8 +248,8 @@ def test_with_json_list_data_decode(self): data = [six.u('foó'), six.u('bár')] channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding class TestBinaryEncodersNoEncryption(BaseTestCase): @@ -281,8 +271,8 @@ def test_text_utf8(self): wraps=channel.ably.http.post) as post_mock: channel.publish('event', six.u('foó')) _, kwargs = post_mock.call_args - self.assertEqual(self.decode(kwargs['body'])['data'], six.u('foó')) - self.assertEqual(self.decode(kwargs['body']).get('encoding', '').strip('/'), '') + assert self.decode(kwargs['body'])['data'] == six.u('foó') + assert self.decode(kwargs['body']).get('encoding', '').strip('/') == '' def test_with_binary_type(self): channel = self.ably.channels["persisted:publish"] @@ -291,8 +281,8 @@ def test_with_binary_type(self): wraps=channel.ably.http.post) as post_mock: channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args - self.assertEqual(self.decode(kwargs['body'])['data'], bytearray(b'foo')) - self.assertEqual(self.decode(kwargs['body']).get('encoding', '').strip('/'), '') + assert self.decode(kwargs['body'])['data'] == bytearray(b'foo') + assert self.decode(kwargs['body']).get('encoding', '').strip('/') == '' def test_with_json_dict_data(self): channel = self.ably.channels["persisted:publish"] @@ -302,9 +292,8 @@ def test_with_json_dict_data(self): channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(self.decode(kwargs['body'])['data']) - self.assertEqual(raw_data, data) - self.assertEqual(self.decode(kwargs['body'])['encoding'].strip('/'), - 'json') + assert raw_data == data + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'json' def test_with_json_list_data(self): channel = self.ably.channels["persisted:publish"] @@ -314,42 +303,41 @@ def test_with_json_list_data(self): channel.publish('event', data) _, kwargs = post_mock.call_args raw_data = json.loads(self.decode(kwargs['body'])['data']) - self.assertEqual(raw_data, data) - self.assertEqual(self.decode(kwargs['body'])['encoding'].strip('/'), - 'json') + assert raw_data == data + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'json' def test_text_utf8_decode(self): channel = self.ably.channels["persisted:stringdecode-bin"] channel.publish('event', six.u('fóo')) message = channel.history().items[0] - self.assertEqual(message.data, six.u('fóo')) - self.assertIsInstance(message.data, six.text_type) - self.assertFalse(message.encoding) + assert message.data == six.u('fóo') + assert isinstance(message.data, six.text_type) + assert not message.encoding def test_with_binary_type_decode(self): channel = self.ably.channels["persisted:binarydecode-bin"] channel.publish('event', bytearray(b'foob')) message = channel.history().items[0] - self.assertEqual(message.data, bytearray(b'foob')) - self.assertFalse(message.encoding) + assert message.data == bytearray(b'foob') + assert not message.encoding def test_with_json_dict_data_decode(self): channel = self.ably.channels["persisted:jsondict-bin"] data = {six.u('foó'): six.u('bár')} channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding def test_with_json_list_data_decode(self): channel = self.ably.channels["persisted:jsonarray-bin"] data = [six.u('foó'), six.u('bár')] channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding class TestBinaryEncodersEncryption(BaseTestCase): @@ -377,10 +365,9 @@ def test_text_utf8(self): wraps=channel.ably.http.post) as post_mock: channel.publish('event', six.u('fóo')) _, kwargs = post_mock.call_args - self.assertEquals(self.decode(kwargs['body'])['encoding'].strip('/'), - 'utf-8/cipher+aes-128-cbc') + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'utf-8/cipher+aes-128-cbc' data = self.decrypt(self.decode(kwargs['body'])['data']).decode('utf-8') - self.assertEquals(data, six.u('fóo')) + assert data == six.u('fóo') def test_with_binary_type(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -391,11 +378,10 @@ def test_with_binary_type(self): channel.publish('event', bytearray(b'foo')) _, kwargs = post_mock.call_args - self.assertEquals(self.decode(kwargs['body'])['encoding'].strip('/'), - 'cipher+aes-128-cbc') + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'cipher+aes-128-cbc' data = self.decrypt(self.decode(kwargs['body'])['data']) - self.assertEqual(data, bytearray(b'foo')) - self.assertIsInstance(data, bytearray) + assert data == bytearray(b'foo') + assert isinstance(data, bytearray) def test_with_json_dict_data(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -405,10 +391,9 @@ def test_with_json_dict_data(self): wraps=channel.ably.http.post) as post_mock: channel.publish('event', data) _, kwargs = post_mock.call_args - self.assertEquals(self.decode(kwargs['body'])['encoding'].strip('/'), - 'json/utf-8/cipher+aes-128-cbc') + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc' raw_data = self.decrypt(self.decode(kwargs['body'])['data']).decode('ascii') - self.assertEqual(json.loads(raw_data), data) + assert json.loads(raw_data) == data def test_with_json_list_data(self): channel = self.ably.channels.get("persisted:publish_enc", @@ -418,19 +403,18 @@ def test_with_json_list_data(self): wraps=channel.ably.http.post) as post_mock: channel.publish('event', data) _, kwargs = post_mock.call_args - self.assertEquals(self.decode(kwargs['body'])['encoding'].strip('/'), - 'json/utf-8/cipher+aes-128-cbc') + assert self.decode(kwargs['body'])['encoding'].strip('/') == 'json/utf-8/cipher+aes-128-cbc' raw_data = self.decrypt(self.decode(kwargs['body'])['data']).decode('ascii') - self.assertEqual(json.loads(raw_data), data) + assert json.loads(raw_data) == data def test_text_utf8_decode(self): channel = self.ably.channels.get("persisted:enc_stringdecode-bin", cipher=self.cipher_params) channel.publish('event', six.u('foó')) message = channel.history().items[0] - self.assertEqual(message.data, six.u('foó')) - self.assertIsInstance(message.data, six.text_type) - self.assertFalse(message.encoding) + assert message.data == six.u('foó') + assert isinstance(message.data, six.text_type) + assert not message.encoding def test_with_binary_type_decode(self): channel = self.ably.channels.get("persisted:enc_binarydecode-bin", @@ -438,9 +422,9 @@ def test_with_binary_type_decode(self): channel.publish('event', bytearray(b'foob')) message = channel.history().items[0] - self.assertEqual(message.data, bytearray(b'foob')) - self.assertIsInstance(message.data, bytearray) - self.assertFalse(message.encoding) + assert message.data == bytearray(b'foob') + assert isinstance(message.data, bytearray) + assert not message.encoding def test_with_json_dict_data_decode(self): channel = self.ably.channels.get("persisted:enc_jsondict-bin", @@ -448,8 +432,8 @@ def test_with_json_dict_data_decode(self): data = {six.u('foó'): six.u('bár')} channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding def test_with_json_list_data_decode(self): channel = self.ably.channels.get("persisted:enc_list-bin", @@ -457,5 +441,5 @@ def test_with_json_list_data_decode(self): data = [six.u('foó'), six.u('bár')] channel.publish('event', data) message = channel.history().items[0] - self.assertEqual(message.data, data) - self.assertFalse(message.encoding) + assert message.data == data + assert not message.encoding From 020585e43e0903f410c973acd9b2adadd26651e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 3 Aug 2018 16:38:05 +0200 Subject: [PATCH 47/85] Tests: refactor calls to AblyRest(...) Fixes #109 --- test/ably/encoders_test.py | 28 +--- test/ably/restauth_test.py | 140 +++++------------- test/ably/restcapability_test.py | 7 +- test/ably/restchannelhistory_test.py | 7 +- test/ably/restchannelpublish_test.py | 39 ++--- test/ably/restchannels_test.py | 14 +- test/ably/resthttp_test.py | 8 +- test/ably/restinit_test.py | 8 +- test/ably/restpaginatedresult_test.py | 10 +- test/ably/restpresence_test.py | 14 +- test/ably/restpush_test.py | 10 +- test/ably/restrequest_test.py | 6 +- test/ably/restsetup.py | 46 +++--- ...restappstats_test.py => reststats_test.py} | 16 +- test/ably/resttime_test.py | 24 +-- test/ably/resttoken_test.py | 35 +---- 16 files changed, 103 insertions(+), 309 deletions(-) rename test/ably/{restappstats_test.py => reststats_test.py} (92%) diff --git a/test/ably/encoders_test.py b/test/ably/encoders_test.py index 38875c15..98ed9d82 100644 --- a/test/ably/encoders_test.py +++ b/test/ably/encoders_test.py @@ -8,7 +8,6 @@ import mock import msgpack -from ably import AblyRest from ably import CipherParams from ably.util.crypto import get_cipher from ably.types.message import Message @@ -16,19 +15,13 @@ from test.ably.restsetup import RestSetup from test.ably.utils import BaseTestCase -test_vars = RestSetup.get_test_vars() log = logging.getLogger(__name__) class TestTextEncodersNoEncryption(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + cls.ably = RestSetup.get_ably_rest(use_binary_protocol=False) def test_text_utf8(self): channel = self.ably.channels["persisted:publish"] @@ -145,12 +138,7 @@ def test_decode_with_invalid_encoding(self): class TestTextEncodersEncryption(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + cls.ably = RestSetup.get_ably_rest(use_binary_protocol=False) cls.cipher_params = CipherParams(secret_key='keyfordecrypt_16', algorithm='aes') @@ -255,11 +243,7 @@ def test_with_json_list_data_decode(self): class TestBinaryEncodersNoEncryption(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() def decode(self, data): return msgpack.unpackb(data, encoding='utf-8') @@ -343,11 +327,7 @@ def test_with_json_list_data_decode(self): class TestBinaryEncodersEncryption(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() cls.cipher_params = CipherParams(secret_key='keyfordecrypt_16', algorithm='aes') diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 4b0fd43e..796d99c4 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -57,12 +57,10 @@ def token_callback(token_params): callback_called.append(True) return "this_is_not_really_a_token_request" - ably = AblyRest(key_name=test_vars["keys"][0]["key_name"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - auth_callback=token_callback) + ably = RestSetup.get_ably_rest( + key=None, + key_name=test_vars["keys"][0]["key_name"], + auth_callback=token_callback) try: ably.stats(None) @@ -79,13 +77,7 @@ def test_auth_init_with_key_and_client_id(self): assert ably.auth.client_id == 'testClientId' def test_auth_init_with_token(self): - - ably = AblyRest(token="this_is_not_really_a_token", - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) - + ably = RestSetup.get_ably_rest(key=None, token="this_is_not_really_a_token") assert Auth.Method.TOKEN == ably.auth.auth_mechanism, "Unexpected Auth method mismatch" # RSA11 @@ -163,11 +155,7 @@ def test_with_default_token_params(self): class TestAuthAuthorize(BaseTestCase): def setUp(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest() def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @@ -222,27 +210,20 @@ def test_authorize_adheres_to_request_token(self): def test_with_token_str_https(self): token = self.ably.auth.authorize() token = token.token - ably = AblyRest(token=token, rest_host=test_vars["host"], - port=test_vars["port"], tls_port=test_vars["tls_port"], - tls=True, use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, token=token, tls=True, + use_binary_protocol=self.use_binary_protocol) ably.channels.test_auth_with_token_str.publish('event', 'foo_bar') def test_with_token_str_http(self): token = self.ably.auth.authorize() token = token.token - ably = AblyRest(token=token, rest_host=test_vars["host"], - port=test_vars["port"], tls_port=test_vars["tls_port"], - tls=False, use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, token=token, tls=False, + use_binary_protocol=self.use_binary_protocol) ably.channels.test_auth_with_token_str.publish('event', 'foo_bar') def test_if_default_client_id_is_used(self): - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - client_id='my_client_id', - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(client_id='my_client_id', + use_binary_protocol=self.use_binary_protocol) token = ably.auth.authorize() assert token.client_id == 'my_client_id' @@ -306,14 +287,10 @@ def test_timestamp_is_not_stored(self): def test_client_id_precedence(self): client_id = uuid.uuid4().hex overridden_client_id = uuid.uuid4().hex - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol, - client_id=client_id, - default_token_params={'client_id': overridden_client_id}) + ably = RestSetup.get_ably_rest( + use_binary_protocol=self.use_binary_protocol, + client_id=client_id, + default_token_params={'client_id': overridden_client_id}) token = ably.auth.authorize() assert token.client_id == client_id assert ably.auth.client_id == client_id @@ -345,22 +322,13 @@ def per_protocol_setup(self, use_binary_protocol): self.use_binary_protocol = use_binary_protocol def test_with_key(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + self.ably = RestSetup.get_ably_rest(use_binary_protocol=self.use_binary_protocol) token_details = self.ably.auth.request_token() assert isinstance(token_details, TokenDetails) - ably = AblyRest(token_details=token_details, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, token_details=token_details, + use_binary_protocol=self.use_binary_protocol) channel = self.get_channel_name('test_request_token_with_key') ably.channels[channel].publish('event', 'foo') @@ -372,11 +340,7 @@ def test_with_key(self): def test_with_auth_url_headers_and_params_POST(self): url = 'http://www.example.com' headers = {'foo': 'bar'} - self.ably = AblyRest(auth_url=url, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest(key=None, auth_url=url) auth_params = {'foo': 'auth', 'spam': 'eggs'} token_params = {'foo': 'token'} @@ -401,13 +365,10 @@ def test_with_auth_url_headers_and_params_GET(self): url = 'http://www.example.com' headers = {'foo': 'bar'} - self.ably = AblyRest(auth_url=url, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - auth_headers={'this': 'will_not_be_used'}, - auth_params={'this': 'will_not_be_used'}) + self.ably = RestSetup.get_ably_rest( + key=None, auth_url=url, + auth_headers={'this': 'will_not_be_used'}, + auth_params={'this': 'will_not_be_used'}) auth_params = {'foo': 'auth', 'spam': 'eggs'} token_params = {'foo': 'token'} @@ -431,11 +392,7 @@ def callback(token_params): assert token_params == called_token_params return 'token_string' - self.ably = AblyRest(auth_callback=callback, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest(key=None, auth_callback=callback) token_details = self.ably.auth.request_token( token_params=called_token_params, auth_callback=callback) @@ -455,11 +412,7 @@ def callback(token_params): def test_when_auth_url_has_query_string(self): url = 'http://www.example.com?with=query' headers = {'foo': 'bar'} - self.ably = AblyRest(auth_url=url, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest(key=None, auth_url=url) responses.add(responses.GET, 'http://www.example.com', body='token_string') @@ -470,13 +423,10 @@ def test_when_auth_url_has_query_string(self): @dont_vary_protocol def test_client_id_null_for_anonymous_auth(self): - ably = AblyRest( + ably = RestSetup.get_ably_rest( + key=None, key_name=test_vars["keys"][0]["key_name"], - key_secret=test_vars["keys"][0]["key_secret"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + key_secret=test_vars["keys"][0]["key_secret"]) token = ably.auth.authorize() assert isinstance(token, TokenDetails) @@ -486,12 +436,8 @@ def test_client_id_null_for_anonymous_auth(self): @dont_vary_protocol def test_client_id_null_until_auth(self): client_id = uuid.uuid4().hex - token_ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - default_token_params={'client_id': client_id}) + token_ably = RestSetup.get_ably_rest( + default_token_params={'client_id': client_id}) # before auth, client_id is None assert token_ably.auth.client_id is None @@ -507,12 +453,7 @@ class TestRenewToken(BaseTestCase): def setUp(self): host = test_vars['host'] - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + self.ably = RestSetup.get_ably_rest(use_binary_protocol=False) # with headers self.token_requests = 0 self.publish_attempts = 0 @@ -570,12 +511,10 @@ def test_when_renewable(self): # RSA4a def test_when_not_renewable(self): - self.ably = AblyRest(token='token ID cannot be used to create a new token', - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + self.ably = RestSetup.get_ably_rest( + key=None, + token='token ID cannot be used to create a new token', + use_binary_protocol=False) self.ably.channels[self.channel].publish('evt', 'msg') assert 1 == self.publish_attempts @@ -589,12 +528,9 @@ def test_when_not_renewable(self): # RSA4a def test_when_not_renewable_with_token_details(self): token_details = TokenDetails(token='a_dummy_token') - self.ably = AblyRest( + self.ably = RestSetup.get_ably_rest( + key=None, token_details=token_details, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], use_binary_protocol=False) self.ably.channels[self.channel].publish('evt', 'msg') assert 1 == self.publish_attempts diff --git a/test/ably/restcapability_test.py b/test/ably/restcapability_test.py index aae175d4..fe5bbf7d 100644 --- a/test/ably/restcapability_test.py +++ b/test/ably/restcapability_test.py @@ -3,7 +3,6 @@ import pytest import six -from ably import AblyRest from ably.types.capability import Capability from ably.util.exceptions import AblyException @@ -17,11 +16,7 @@ class TestRestCapability(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol diff --git a/test/ably/restchannelhistory_test.py b/test/ably/restchannelhistory_test.py index 26ba0b49..f608c2e5 100644 --- a/test/ably/restchannelhistory_test.py +++ b/test/ably/restchannelhistory_test.py @@ -8,7 +8,6 @@ from six.moves import range from ably import AblyException -from ably import AblyRest from ably.http.paginatedresult import PaginatedResult from test.ably.restsetup import RestSetup @@ -22,11 +21,7 @@ class TestRestChannelHistory(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index d71a2090..53856164 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -14,7 +14,6 @@ from six.moves import range from ably import AblyException, IncompatibleClientIdException -from ably import AblyRest from ably.rest.auth import Auth from ably.types.message import Message from ably.types.tokendetails import TokenDetails @@ -29,18 +28,9 @@ @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestRestChannelPublish(BaseTestCase): def setUp(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest() self.client_id = uuid.uuid4().hex - self.ably_with_client_id = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - client_id=self.client_id) + self.ably_with_client_id = RestSetup.get_ably_rest(client_id=self.client_id) def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @@ -124,12 +114,7 @@ def test_message_list_generate_one_request(self): assert message['data'] == six.text_type(i) def test_publish_error(self): - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(use_binary_protocol=self.use_binary_protocol) ably.auth.authorize( token_params={'capability': {"only_subscribe": ["subscribe"]}}) @@ -306,12 +291,8 @@ def test_publish_message_with_client_id_on_identified_client(self): def test_publish_message_with_wrong_client_id_on_implicit_identified_client(self): new_token = self.ably.auth.authorize( token_params={'client_id': uuid.uuid4().hex}) - new_ably = AblyRest(token=new_token.token, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + new_ably = RestSetup.get_ably_rest(key=None, token=new_token.token, + use_binary_protocol=self.use_binary_protocol) channel = new_ably.channels[ self.get_channel_name('persisted:wrong_client_id_implicit_client')] @@ -324,12 +305,10 @@ def test_publish_message_with_wrong_client_id_on_implicit_identified_client(self # RSA15b def test_wildcard_client_id_can_publish_as_others(self): wildcard_token_details = self.ably.auth.request_token({'client_id': '*'}) - wildcard_ably = AblyRest(token_details=wildcard_token_details, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + wildcard_ably = RestSetup.get_ably_rest( + key=None, + token_details=wildcard_token_details, + use_binary_protocol=self.use_binary_protocol) assert wildcard_ably.auth.client_id == '*' channel = wildcard_ably.channels[ diff --git a/test/ably/restchannels_test.py b/test/ably/restchannels_test.py index 78c8eeb9..5ca98132 100644 --- a/test/ably/restchannels_test.py +++ b/test/ably/restchannels_test.py @@ -5,7 +5,7 @@ import pytest from six.moves import range -from ably import AblyRest, AblyException +from ably import AblyException from ably.rest.channel import Channel, Channels, Presence from ably.util.crypto import generate_random_key @@ -19,11 +19,7 @@ class TestChannels(BaseTestCase): def setUp(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest() def test_rest_channels_attr(self): assert hasattr(self.ably, 'channels') @@ -96,11 +92,7 @@ def test_channel_has_presence(self): def test_without_permissions(self): key = test_vars["keys"][2] - ably = AblyRest(key=key["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + ably = RestSetup.get_ably_rest(key=key["key_str"]) with pytest.raises(AblyException) as excinfo: ably.channels['test_publish_without_permission'].publish('foo', 'woop') diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index a6836bbb..1970f821 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -15,8 +15,6 @@ from test.ably.restsetup import RestSetup from test.ably.utils import BaseTestCase -test_vars = RestSetup.get_test_vars() - class TestRestHttp(BaseTestCase): def test_max_retry_attempts_and_timeouts_defaults(self): @@ -131,11 +129,7 @@ def test_custom_http_timeouts(self): # RSC7a, RSC7b def test_request_headers(self): - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + ably = RestSetup.get_ably_rest() r = ably.http.make_request('HEAD', '/time', skip_auth=True) # API diff --git a/test/ably/restinit_test.py b/test/ably/restinit_test.py index 6cae2626..3218efef 100644 --- a/test/ably/restinit_test.py +++ b/test/ably/restinit_test.py @@ -156,12 +156,8 @@ def test_with_no_auth_params(self): AblyRest(port=111) def test_query_time_param(self): - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], query_time=True, - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(query_time=True, + use_binary_protocol=self.use_binary_protocol) timestamp = ably.auth._timestamp with patch('ably.rest.rest.AblyRest.time', wraps=ably.time) as server_time,\ diff --git a/test/ably/restpaginatedresult_test.py b/test/ably/restpaginatedresult_test.py index 07ebf398..a6c70d26 100644 --- a/test/ably/restpaginatedresult_test.py +++ b/test/ably/restpaginatedresult_test.py @@ -4,14 +4,11 @@ import responses -from ably import AblyRest from ably.http.paginatedresult import PaginatedResult from test.ably.restsetup import RestSetup from test.ably.utils import BaseTestCase -test_vars = RestSetup.get_test_vars() - class TestPaginatedResult(BaseTestCase): @@ -25,12 +22,7 @@ def callback(request): return callback def setUp(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + self.ably = RestSetup.get_ably_rest(use_binary_protocol=False) # Mocked responses # without headers diff --git a/test/ably/restpresence_test.py b/test/ably/restpresence_test.py index 7e53082d..b5350575 100644 --- a/test/ably/restpresence_test.py +++ b/test/ably/restpresence_test.py @@ -8,7 +8,6 @@ import six import responses -from ably import AblyRest from ably.http.paginatedresult import PaginatedResult from ably.types.presence import PresenceMessage @@ -23,11 +22,7 @@ class TestPresence(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() cls.channel = cls.ably.channels.get('persisted:presence_fixtures') @classmethod @@ -207,12 +202,7 @@ class TestPresenceCrypt(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) - + cls.ably = RestSetup.get_ably_rest() key = b'0123456789abcdef' cls.channel = cls.ably.channels.get('persisted:presence_fixtures', cipher={'key': key}) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index d815e749..56793401 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -5,7 +5,7 @@ import pytest import six -from ably import AblyRest, AblyException, AblyAuthException +from ably import AblyException, AblyAuthException from ably import DeviceDetails, PushChannelSubscription from ably.http.paginatedresult import PaginatedResult @@ -13,8 +13,6 @@ from test.ably.utils import VaryByProtocolTestsMetaclass, BaseTestCase from test.ably.utils import new_dict, random_string, get_random_key -test_vars = RestSetup.get_test_vars() - DEVICE_TOKEN = '740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad' @@ -24,11 +22,7 @@ class TestPush(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() # Register several devices for later use cls.devices = {} diff --git a/test/ably/restrequest_test.py b/test/ably/restrequest_test.py index bb2f07ca..19fe011d 100644 --- a/test/ably/restrequest_test.py +++ b/test/ably/restrequest_test.py @@ -17,11 +17,7 @@ class TestRestRequest(BaseTestCase): @classmethod def setUpClass(cls): - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + cls.ably = RestSetup.get_ably_rest() # Populate the channel (using the new api) cls.channel = cls.get_channel_name() diff --git a/test/ably/restsetup.py b/test/ably/restsetup.py index 2ebb2b36..debc47a0 100644 --- a/test/ably/restsetup.py +++ b/test/ably/restsetup.py @@ -4,7 +4,6 @@ import os import logging -from ably.http.httputils import HttpUtils from ably.rest.rest import AblyRest from ably.types.capability import Capability from ably.types.options import Options @@ -17,26 +16,22 @@ app_spec_local = json.loads(f.read()) tls = (os.environ.get('ABLY_TLS') or "true").lower() == "true" -host = os.environ.get('ABLY_HOST') +host = os.environ.get('ABLY_HOST', 'sandbox-rest.ably.io') +environment = os.environ.get('ABLY_ENV') port = 80 tls_port = 443 -if host is None: - host = "sandbox-rest.ably.io" - -if host.endswith("rest.ably.io"): - host = "sandbox-rest.ably.io" - port = 80 - tls_port = 443 -else: + +if host and not host.endswith("rest.ably.io"): tls = tls and not host.equals("localhost") port = 8080 tls_port = 8081 ably = AblyRest(token='not_a_real_token', rest_host=host, - port=port, tls_port=tls_port, - tls=tls, use_binary_protocol=False) + port=port, tls_port=tls_port, tls=tls, + environment=environment, + use_binary_protocol=False) class RestSetup: @@ -58,6 +53,7 @@ def get_test_vars(sender=None): "port": port, "tls_port": tls_port, "tls": tls, + "environment": environment, "keys": [{ "key_name": "%s.%s" % (app_id, k.get("id", "")), "key_secret": k.get("value", ""), @@ -71,20 +67,28 @@ def get_test_vars(sender=None): for k in app_spec.get("keys", [])]) return RestSetup.__test_vars - @staticmethod - def clear_test_vars(): + @classmethod + def get_ably_rest(cls, **kw): + test_vars = RestSetup.get_test_vars() + options = { + 'key': test_vars["keys"][0]["key_str"], + 'rest_host': test_vars["host"], + 'port': test_vars["port"], + 'tls_port': test_vars["tls_port"], + 'tls': test_vars["tls"], + 'environment': test_vars["environment"], + } + options.update(kw) + return AblyRest(**options) + + @classmethod + def clear_test_vars(cls): test_vars = RestSetup.__test_vars options = Options(key=test_vars["keys"][0]["key_str"]) options.rest_host = test_vars["host"] options.port = test_vars["port"] options.tls_port = test_vars["tls_port"] options.tls = test_vars["tls"] - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host = test_vars["host"], - port = test_vars["port"], - tls_port = test_vars["tls_port"], - tls = test_vars["tls"]) - + ably = cls.get_ably_rest() ably.http.delete('/apps/' + test_vars['app_id']) - RestSetup.__test_vars = None diff --git a/test/ably/restappstats_test.py b/test/ably/reststats_test.py similarity index 92% rename from test/ably/restappstats_test.py rename to test/ably/reststats_test.py index 10ba79a6..90fb1ee0 100644 --- a/test/ably/restappstats_test.py +++ b/test/ably/reststats_test.py @@ -8,7 +8,6 @@ import pytest import six -from ably import AblyRest from ably.types.stats import Stats from ably.util.exceptions import AblyException from ably.http.paginatedresult import PaginatedResult @@ -17,7 +16,6 @@ from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseTestCase log = logging.getLogger(__name__) -test_vars = RestSetup.get_test_vars() class TestRestAppStatsSetup(object): @@ -34,18 +32,8 @@ def get_params(cls): @classmethod def setUpClass(cls): RestSetup._RestSetup__test_vars = None - test_vars = RestSetup.get_test_vars() - cls.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) - cls.ably_text = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=False) + cls.ably = RestSetup.get_ably_rest() + cls.ably_text = RestSetup.get_ably_rest(use_binary_protocol=False) cls.last_year = datetime.now().year - 1 cls.previous_year = datetime.now().year - 2 diff --git a/test/ably/resttime_test.py b/test/ably/resttime_test.py index efb9c652..eac933bd 100644 --- a/test/ably/resttime_test.py +++ b/test/ably/resttime_test.py @@ -6,13 +6,10 @@ import six from ably import AblyException -from ably import AblyRest from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseTestCase -test_vars = RestSetup.get_test_vars() - @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestRestTime(BaseTestCase): @@ -21,12 +18,7 @@ def per_protocol_setup(self, use_binary_protocol): self.use_binary_protocol = use_binary_protocol def test_time_accuracy(self): - ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(use_binary_protocol=self.use_binary_protocol) reported_time = ably.time() actual_time = time.time() * 1000.0 @@ -35,12 +27,8 @@ def test_time_accuracy(self): assert abs(actual_time - reported_time) < seconds * 1000, "Time is not within %s seconds" % seconds def test_time_without_key_or_token(self): - ably = AblyRest(token='foo', - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, token='foo', + use_binary_protocol=self.use_binary_protocol) reported_time = ably.time() actual_time = time.time() * 1000.0 @@ -50,10 +38,6 @@ def test_time_without_key_or_token(self): @dont_vary_protocol def test_time_fails_without_valid_host(self): - ably = AblyRest(token='foo', - rest_host="this.host.does.not.exist", - port=test_vars["port"], - tls_port=test_vars["tls_port"]) - + ably = RestSetup.get_ably_rest(key=None, token='foo', rest_host="this.host.does.not.exist") with pytest.raises(AblyException): ably.time() diff --git a/test/ably/resttoken_test.py b/test/ably/resttoken_test.py index 07d5671e..2c75e0a2 100644 --- a/test/ably/resttoken_test.py +++ b/test/ably/resttoken_test.py @@ -17,7 +17,6 @@ from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseTestCase -test_vars = RestSetup.get_test_vars() log = logging.getLogger(__name__) @@ -30,11 +29,7 @@ def server_time(self): def setUp(self): capability = {"*": ["*"]} self.permit_all = six.text_type(Capability(capability)) - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest() def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @@ -163,11 +158,7 @@ def test_request_token_float_and_timedelta(self): class TestCreateTokenRequest(BaseTestCase): def setUp(self): - self.ably = AblyRest(key=test_vars["keys"][0]["key_str"], - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + self.ably = RestSetup.get_ably_rest() self.key_name = self.ably.options.key_name self.key_secret = self.ably.options.key_secret @@ -177,11 +168,7 @@ def per_protocol_setup(self, use_binary_protocol): @dont_vary_protocol def test_key_name_and_secret_are_required(self): - ably = AblyRest(token='not a real token', - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"]) + ably = RestSetup.get_ably_rest(key=None, token='not a real token') with pytest.raises(AblyException, match="40101 401 No key specified"): ably.auth.create_token_request() with pytest.raises(AblyException, match="40101 401 No key specified"): @@ -217,12 +204,8 @@ def test_token_request_can_be_used_to_get_a_token(self): def auth_callback(token_params): return token_request - ably = AblyRest(auth_callback=auth_callback, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, auth_callback=auth_callback, + use_binary_protocol=self.use_binary_protocol) token = ably.auth.authorize() @@ -296,12 +279,8 @@ def test_capability(self): def auth_callback(token_params): return token_request - ably = AblyRest(auth_callback=auth_callback, - rest_host=test_vars["host"], - port=test_vars["port"], - tls_port=test_vars["tls_port"], - tls=test_vars["tls"], - use_binary_protocol=self.use_binary_protocol) + ably = RestSetup.get_ably_rest(key=None, auth_callback=auth_callback, + use_binary_protocol=self.use_binary_protocol) token = ably.auth.authorize() From f60fcd539ad8c05844a1e427fab181db3d510260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 24 Jul 2018 17:31:17 +0200 Subject: [PATCH 48/85] RSH1c2 New push.admin.channel_subscriptions.list_channels --- ably/rest/push.py | 14 ++++++++ ably/types/channelsubscription.py | 4 +++ test/ably/restpush_test.py | 59 +++++++++++++++++++++++-------- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index 7a63b9ff..99fe52eb 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -1,6 +1,8 @@ from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.device import DeviceDetails, device_details_response_processor from ably.types.channelsubscription import PushChannelSubscription, channel_subscriptions_response_processor +from ably.types.channelsubscription import channels_response_processor + class Push(object): @@ -142,6 +144,18 @@ def list(self, **params): self.ably.http, url=path, response_processor=channel_subscriptions_response_processor) + def list_channels(self, **params): + """Returns a PaginatedResult object with the list of + PushChannelSubscription objects, filtered by the given parameters. + + :Parameters: + - `**params`: the parameters used to filter the list + """ + path = '/push/channels' + format_params(params) + response_processor = channels_response_processor + return PaginatedResult.paginated_query( + self.ably.http, url=path, response_processor=response_processor) + def save(self, subscription): """Creates or updates the subscription. Returns a PushChannelSubscription object. diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index 7022b81b..d9fed708 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -56,3 +56,7 @@ def factory(cls, subscription): def channel_subscriptions_response_processor(response): native = response.to_native() return PushChannelSubscription.from_array(native) + +def channels_response_processor(response): + native = response.to_native() + return native diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 56793401..2bc58760 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -1,3 +1,4 @@ +import itertools import random import string import time @@ -29,6 +30,12 @@ def setUpClass(cls): for i in range(10): cls.save_device() + # Register several subscriptions for later use + cls.channels = {'canpublish:test1': [], 'canpublish:test2': [], 'canpublish:test3': []} + for key, channel in zip(cls.devices, itertools.cycle(cls.channels)): + device = cls.devices[key] + cls.save_subscription(channel, device_id=device.id) + def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @@ -98,6 +105,23 @@ def get_device(cls): key = get_random_key(cls.devices) return cls.devices[key] + @classmethod + def get_channel(cls): + key = get_random_key(cls.channels) + return key, cls.channels[key] + + @classmethod + def save_subscription(cls, channel, **kw): + """ + Helper method to register a device, to not have this code repeated + everywhere. Returns the input dict that was sent to Ably, and the + device details returned by Ably. + """ + subscription = PushChannelSubscription(channel, **kw) + subscription = cls.ably.push.admin.channel_subscriptions.save(subscription) + cls.channels.setdefault(channel, []).append(subscription) + return subscription + # RSH1a def test_admin_publish(self): recipient = {'clientId': 'ablyChannel'} @@ -221,15 +245,7 @@ def test_admin_device_registrations_remove_where(self): def test_admin_channel_subscriptions_list(self): list_ = self.ably.push.admin.channel_subscriptions.list - channel = 'canpublish:test1' - - # Register several channel subscriptions for later use - ids = set() - save = self.ably.push.admin.channel_subscriptions.save - for key in self.devices: - device = self.devices[key] - save(PushChannelSubscription(channel, device_id=device.id)) - ids.add(device.id) + channel, subscriptions = self.get_channel() response = list_(channel=channel) assert type(response) is PaginatedResult @@ -237,22 +253,35 @@ def test_admin_channel_subscriptions_list(self): assert type(response.items[0]) is PushChannelSubscription # limit - assert len(list_(channel=channel, limit=5000).items) == len(self.devices) + assert len(list_(channel=channel, limit=5000).items) == len(subscriptions) assert len(list_(channel=channel, limit=2).items) == 2 # Filter by device id - device = self.get_device() - items = list_(channel=channel, deviceId=device.id).items + device_id = subscriptions[0].device_id + items = list_(channel=channel, deviceId=device_id).items assert len(items) == 1 - assert items[0].device_id == device.id + assert items[0].device_id == device_id assert items[0].channel == channel - assert device.id in ids assert len(list_(channel=channel, deviceId=self.get_device_id()).items) == 0 # Filter by client id + device = self.get_device() assert len(list_(channel=channel, clientId=device.client_id).items) == 0 + # RSH1c2 + def test_admin_channels_list(self): + list_ = self.ably.push.admin.channel_subscriptions.list_channels + + response = list_() + assert type(response) is PaginatedResult + assert type(response.items) is list + assert type(response.items[0]) is str + + # limit + assert len(list_(limit=5000).items) == len(self.channels) + assert len(list_(limit=2).items) == 2 + # RSH1c3 def test_admin_channel_subscriptions_save(self): save = self.ably.push.admin.channel_subscriptions.save @@ -262,7 +291,7 @@ def test_admin_channel_subscriptions_save(self): # Subscribe channel = 'canpublish:test' - subscription = save(PushChannelSubscription(channel, device_id=device.id)) + subscription = self.save_subscription(channel, device_id=device.id) assert type(subscription) is PushChannelSubscription assert subscription.channel == channel assert subscription.device_id == device.id From b7c2001edcf54055384a034e06cb09215b7436fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 6 Aug 2018 10:12:46 +0200 Subject: [PATCH 49/85] Fix tests for Python 2 --- test/ably/restpush_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 2bc58760..988043c7 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -276,7 +276,7 @@ def test_admin_channels_list(self): response = list_() assert type(response) is PaginatedResult assert type(response.items) is list - assert type(response.items[0]) is str + assert type(response.items[0]) is six.text_type # limit assert len(list_(limit=5000).items) == len(self.channels) From cd9bbd2d8736df61ee4cc190d309942c1be46e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 6 Aug 2018 11:15:57 +0200 Subject: [PATCH 50/85] Feedback from review --- ably/rest/push.py | 4 ++-- test/ably/restpush_test.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index 99fe52eb..ac593e19 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -152,9 +152,9 @@ def list_channels(self, **params): - `**params`: the parameters used to filter the list """ path = '/push/channels' + format_params(params) - response_processor = channels_response_processor return PaginatedResult.paginated_query( - self.ably.http, url=path, response_processor=response_processor) + self.ably.http, url=path, + response_processor=channels_response_processor) def save(self, subscription): """Creates or updates the subscription. Returns a diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 988043c7..17c609dd 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -35,6 +35,7 @@ def setUpClass(cls): for key, channel in zip(cls.devices, itertools.cycle(cls.channels)): device = cls.devices[key] cls.save_subscription(channel, device_id=device.id) + assert len(list(itertools.chain(*cls.channels.values()))) == len(cls.devices) def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol From ff47354d66747fdfaf90542c029a32687689de1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 11:19:53 +0200 Subject: [PATCH 51/85] RSH1c4 New push.admin.channel_subscriptions.remove --- ably/rest/push.py | 11 +++++++++++ ably/types/channelsubscription.py | 9 ++++++++- test/ably/restpush_test.py | 28 +++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/ably/rest/push.py b/ably/rest/push.py index ac593e19..1499fd95 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -169,3 +169,14 @@ def save(self, subscription): response = self.ably.http.post(path, body=body) obj = response.to_native() return PushChannelSubscription.from_dict(obj) + + def remove(self, subscription): + """Deletes the given subscription. + + :Parameters: + - `subscription`: the subscription object to remove + """ + subscription = PushChannelSubscription.factory(subscription) + params = subscription.as_dict() + path = '/push/channelSubscriptions' + format_params(params) + return self.ably.http.delete(path) diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index d9fed708..3ac0ec53 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -33,7 +33,14 @@ def app_id(self): def as_dict(self): keys = ['channel', 'device_id', 'client_id', 'app_id'] - obj = {snake_to_camel(key): getattr(self, key) for key in keys} + + obj = {} + for key in keys: + value = getattr(self, key) + if value is not None: + key = snake_to_camel(key) + obj[key] = value + return obj @classmethod diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 17c609dd..d2ef79ae 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -281,17 +281,15 @@ def test_admin_channels_list(self): # limit assert len(list_(limit=5000).items) == len(self.channels) - assert len(list_(limit=2).items) == 2 + assert len(list_(limit=1).items) == 1 # RSH1c3 def test_admin_channel_subscriptions_save(self): save = self.ably.push.admin.channel_subscriptions.save - # Register device - device = self.get_device() - # Subscribe - channel = 'canpublish:test' + device = self.get_device() + channel = 'canpublish:testsave' subscription = self.save_subscription(channel, device_id=device.id) assert type(subscription) is PushChannelSubscription assert subscription.channel == channel @@ -310,3 +308,23 @@ def test_admin_channel_subscriptions_save(self): subscription = PushChannelSubscription(channel, device_id='notregistered') with pytest.raises(AblyException): save(subscription) + + # RSH1c4 + def test_admin_channel_subscriptions_remove(self): + save = self.ably.push.admin.channel_subscriptions.save + remove = self.ably.push.admin.channel_subscriptions.remove + + channel = 'canpublish:testremove' + + # Subscribe device + device = self.get_device() + subscription = save(PushChannelSubscription(channel, device_id=device.id)) + assert remove(subscription).status_code == 204 + + # Subscribe client + client_id = self.get_client_id() + subscription = save(PushChannelSubscription(channel, client_id=client_id)) + assert remove(subscription).status_code == 204 + + # Remove again, it doesn't fail + assert remove(subscription).status_code == 204 From d822faf5556a4a2fd71b97dc58d4f00a345fb4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 7 Aug 2018 17:04:14 +0200 Subject: [PATCH 52/85] RSH1c4 Test the subscription is gone --- test/ably/restpush_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index d2ef79ae..9cc9516e 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -313,18 +313,23 @@ def test_admin_channel_subscriptions_save(self): def test_admin_channel_subscriptions_remove(self): save = self.ably.push.admin.channel_subscriptions.save remove = self.ably.push.admin.channel_subscriptions.remove + list_ = self.ably.push.admin.channel_subscriptions.list channel = 'canpublish:testremove' # Subscribe device device = self.get_device() subscription = save(PushChannelSubscription(channel, device_id=device.id)) + assert device.id in (x.device_id for x in list_(channel=channel).items) assert remove(subscription).status_code == 204 + assert device.id not in (x.device_id for x in list_(channel=channel).items) # Subscribe client client_id = self.get_client_id() subscription = save(PushChannelSubscription(channel, client_id=client_id)) + assert client_id in (x.client_id for x in list_(channel=channel).items) assert remove(subscription).status_code == 204 + assert client_id not in (x.client_id for x in list_(channel=channel).items) # Remove again, it doesn't fail assert remove(subscription).status_code == 204 From 065d2488b20544e29200083c04624289d4ef964e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 25 Jul 2018 12:16:22 +0200 Subject: [PATCH 53/85] RSH1c5 New push.admin.channel_subscriptions.remove_where --- ably/http/paginatedresult.py | 2 ++ ably/rest/push.py | 10 +++++++++- ably/types/channelsubscription.py | 6 +++--- ably/types/device.py | 6 +++--- ably/{types/utils.py => util/case.py} | 0 test/ably/restpush_test.py | 25 +++++++++++++++++++++++++ 6 files changed, 42 insertions(+), 7 deletions(-) rename ably/{types/utils.py => util/case.py} (100%) diff --git a/ably/http/paginatedresult.py b/ably/http/paginatedresult.py index b275be7c..02622a5a 100644 --- a/ably/http/paginatedresult.py +++ b/ably/http/paginatedresult.py @@ -6,6 +6,7 @@ from six.moves.urllib.parse import urlencode from ably.http.http import Request +from ably.util import case log = logging.getLogger(__name__) @@ -22,6 +23,7 @@ def format_params(params=None, direction=None, start=None, end=None, limit=None, for key, value in kw.items(): if value is not None: + key = case.snake_to_camel(key) params[key] = value if direction: diff --git a/ably/rest/push.py b/ably/rest/push.py index 1499fd95..dfb7687e 100644 --- a/ably/rest/push.py +++ b/ably/rest/push.py @@ -178,5 +178,13 @@ def remove(self, subscription): """ subscription = PushChannelSubscription.factory(subscription) params = subscription.as_dict() - path = '/push/channelSubscriptions' + format_params(params) + return self.remove_where(**params) + + def remove_where(self, **params): + """Deletes the subscriptions identified by the given parameters. + + :Parameters: + - `**params`: the parameters that identify the subscriptions to remove + """ + path = '/push/channelSubscriptions' + format_params(**params) return self.ably.http.delete(path) diff --git a/ably/types/channelsubscription.py b/ably/types/channelsubscription.py index 3ac0ec53..aa392fc5 100644 --- a/ably/types/channelsubscription.py +++ b/ably/types/channelsubscription.py @@ -1,4 +1,4 @@ -from .utils import camel_to_snake, snake_to_camel +from ably.util import case class PushChannelSubscription(object): @@ -38,14 +38,14 @@ def as_dict(self): for key in keys: value = getattr(self, key) if value is not None: - key = snake_to_camel(key) + key = case.snake_to_camel(key) obj[key] = value return obj @classmethod def from_dict(cls, obj): - obj = {camel_to_snake(key): value for key, value in obj.items()} + obj = {case.camel_to_snake(key): value for key, value in obj.items()} return cls(**obj) @classmethod diff --git a/ably/types/device.py b/ably/types/device.py index 35c7e583..57dd0fae 100644 --- a/ably/types/device.py +++ b/ably/types/device.py @@ -1,4 +1,4 @@ -from .utils import camel_to_snake, snake_to_camel +from ably.util import case DevicePushTransportType = {'fcm', 'gcm', 'apns', 'web'} @@ -79,14 +79,14 @@ def as_dict(self): for key in keys: value = getattr(self, key) if value is not None: - key = snake_to_camel(key) + key = case.snake_to_camel(key) obj[key] = value return obj @classmethod def from_dict(cls, obj): - obj = {camel_to_snake(key): value for key, value in obj.items()} + obj = {case.camel_to_snake(key): value for key, value in obj.items()} return cls(**obj) @classmethod diff --git a/ably/types/utils.py b/ably/util/case.py similarity index 100% rename from ably/types/utils.py rename to ably/util/case.py diff --git a/test/ably/restpush_test.py b/test/ably/restpush_test.py index 9cc9516e..d339ebdd 100644 --- a/test/ably/restpush_test.py +++ b/test/ably/restpush_test.py @@ -333,3 +333,28 @@ def test_admin_channel_subscriptions_remove(self): # Remove again, it doesn't fail assert remove(subscription).status_code == 204 + + # RSH1c5 + def test_admin_channel_subscriptions_remove_where(self): + save = self.ably.push.admin.channel_subscriptions.save + remove = self.ably.push.admin.channel_subscriptions.remove_where + list_ = self.ably.push.admin.channel_subscriptions.list + + channel = 'canpublish:testremovewhere' + + # Subscribe device + device = self.get_device() + save(PushChannelSubscription(channel, device_id=device.id)) + assert device.id in (x.device_id for x in list_(channel=channel).items) + assert remove(channel=channel, device_id=device.id).status_code == 204 + assert device.id not in (x.device_id for x in list_(channel=channel).items) + + # Subscribe client + client_id = self.get_client_id() + save(PushChannelSubscription(channel, client_id=client_id)) + assert client_id in (x.client_id for x in list_(channel=channel).items) + assert remove(channel=channel, client_id=client_id).status_code == 204 + assert client_id not in (x.client_id for x in list_(channel=channel).items) + + # Remove again, it doesn't fail + assert remove(channel=channel, client_id=client_id).status_code == 204 From e6392949c756c5d5b001184998b3e43db898a004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 10:06:14 +0200 Subject: [PATCH 54/85] TO3n idempotentRestPublishing --- ably/types/options.py | 10 ++++++++++ test/ably/restchannelpublish_test.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ably/types/options.py b/ably/types/options.py index cb901601..af336099 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -13,6 +13,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, http_open_timeout=None, http_request_timeout=None, http_max_retry_count=None, http_max_retry_duration=None, fallback_hosts=None, fallback_hosts_use_default=None, + idempotent_rest_publishing=None, **kwargs): super(Options, self).__init__(**kwargs) @@ -21,6 +22,10 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, if environment is not None and rest_host is not None: raise ValueError('specify rest_host or environment, not both') + if idempotent_rest_publishing is None: + from ably import api_version + idempotent_rest_publishing = api_version >= '1.1' + self.__client_id = client_id self.__log_level = log_level self.__tls = tls @@ -38,6 +43,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, self.__http_max_retry_duration = http_max_retry_duration self.__fallback_hosts = fallback_hosts self.__fallback_hosts_use_default = fallback_hosts_use_default + self.__idempotent_rest_publishing = idempotent_rest_publishing self.__rest_hosts = self.__get_rest_hosts() @@ -165,6 +171,10 @@ def fallback_hosts(self): def fallback_hosts_use_default(self): return self.__fallback_hosts_use_default + @property + def idempotent_rest_publishing(self): + return self.__idempotent_rest_publishing + def __get_rest_hosts(self): """ Return the list of hosts as they should be tried. First comes the main diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 53856164..e4fe83cc 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -13,6 +13,7 @@ import six from six.moves import range +from ably import api_version from ably import AblyException, IncompatibleClientIdException from ably.rest.auth import Auth from ably.types.message import Message @@ -403,3 +404,24 @@ def test_interoperability(self): message = history.items[0] assert message.data == expected_value assert type(message.data) == type_mapping[expected_type] + + +class TestRestChannelPublishIdempotent(BaseTestCase): + + def setUp(self): + self.ably = RestSetup.get_ably_rest() + + # TO3n + def test_idempotent_rest_publishing(self): + # Test default value + if api_version < '1.1': + assert self.ably.options.idempotent_rest_publishing is False + else: + assert self.ably.options.idempotent_rest_publishing is True + + # Test setting value explicitly + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True) + assert ably.options.idempotent_rest_publishing is True + + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=False) + assert ably.options.idempotent_rest_publishing is False From fc6144d24630caadfc3203797c051adbe2c24662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 12:15:14 +0200 Subject: [PATCH 55/85] RSL1j Test message serialization on publish --- ably/rest/channel.py | 66 +++++++++++++++------------- ably/types/message.py | 21 +-------- test/ably/restchannelpublish_test.py | 27 +++++++++++- 3 files changed, 62 insertions(+), 52 deletions(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index ed71e894..e75896b6 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -9,8 +9,7 @@ from six.moves.urllib.parse import quote from ably.http.paginatedresult import PaginatedResult, format_params -from ably.types.message import ( - Message, make_message_response_handler, MessageJSONEncoder) +from ably.types.message import Message, make_message_response_handler from ably.types.presence import Presence from ably.util.crypto import get_cipher from ably.util.exceptions import catch_all, IncompatibleClientIdException @@ -38,18 +37,10 @@ def history(self, direction=None, limit=None, start=None, end=None, timeout=None return PaginatedResult.paginated_query( self.ably.http, url=path, response_processor=message_handler) - @catch_all - def publish(self, name=None, data=None, client_id=None, extras=None, - messages=None, timeout=None): - """Publishes a message on this channel. - - :Parameters: - - `name`: the name for this message. - - `data`: the data for this message. - - `messages`: list of `Message` objects to be published. - Specify this param OR `name` and `data`. - - :attention: You can publish using `name` and `data` OR `messages`, never all three. + def __publish_request_body(self, name=None, data=None, client_id=None, + extras=None, messages=None): + """ + Helper private method, separated from publish() to test RSL1j """ if not messages: messages = [Message(name, data, client_id, extras=extras)] @@ -71,24 +62,37 @@ def publish(self, name=None, data=None, client_id=None, extras=None, request_body_list.append(m) + request_body = [ + message.as_dict(binary=self.ably.options.use_binary_protocol) + for message in request_body_list] + + if len(request_body) == 1: + request_body = request_body[0] + + return request_body + + @catch_all + def publish(self, name=None, data=None, client_id=None, extras=None, + messages=None, timeout=None): + """Publishes a message on this channel. + + :Parameters: + - `name`: the name for this message. + - `data`: the data for this message. + - `messages`: list of `Message` objects to be published. + Specify this param OR `name` and `data`. + + :attention: You can publish using `name` and `data` OR `messages`, never all three. + """ + request_body = self.__publish_request_body(name, data, client_id, extras, messages) + if not self.ably.options.use_binary_protocol: - if len(request_body_list) == 1: - request_body = request_body_list[0].as_json() - else: - request_body = json.dumps(request_body_list, cls=MessageJSONEncoder) + request_body = json.dumps(request_body, separators=(',', ':')) else: - request_body = [message.as_dict(binary=True) for message in request_body_list] - if len(request_body) == 1: - request_body = request_body[0] request_body = msgpack.packb(request_body, use_bin_type=True) path = '/channels/%s/publish' % self.__name - - return self.ably.http.post( - path, - body=request_body, - timeout=timeout - ) + return self.ably.http.post(path, body=request_body, timeout=timeout) @property def ably(self): @@ -119,10 +123,10 @@ def options(self, options): self.__options = options if options and 'cipher' in options: - if options.get('cipher') is not None: - self.__cipher = get_cipher(options.get('cipher')) - else: - self.__cipher = None + cipher = options.get('cipher') + if cipher is not None: + cipher = get_cipher(cipher) + self.__cipher = cipher class Channels(object): diff --git a/ably/types/message.py b/ably/types/message.py index de5db32f..94845992 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -6,7 +6,6 @@ import time import six -import msgpack from ably.types.typedbuffer import TypedBuffer from ably.types.mixins import EncodeDataMixin @@ -28,9 +27,7 @@ def __init__(self, name=None, data=None, client_id=None, extras=None, elif isinstance(name, six.binary_type): self.__name = name.decode('ascii') else: - # log.debug(name) - # log.debug(name.__class__) - raise ValueError("name must be a string or bytes") + raise ValueError("name must be a string or bytes, not %s" % type(name)) self.__id = id self.__client_id = client_id @@ -190,17 +187,11 @@ def as_dict(self, binary=False): if self.connection_key: request_body['connectionKey'] = self.connection_key - if self.extras: + if self.extras is not None: request_body['extras'] = self.extras return request_body - def as_json(self): - return json.dumps(self.as_dict(), separators=(',', ':')) - - def as_msgpack(self): - return msgpack.packb(self.as_dict(binary=True), use_bin_type=True) - @staticmethod def from_encoded(obj, cipher=None): id = obj.get('id') @@ -230,11 +221,3 @@ def encrypted_message_response_handler(response): messages = response.to_native() return Message.from_encoded_array(messages, cipher=cipher) return encrypted_message_response_handler - - -class MessageJSONEncoder(json.JSONEncoder): - def default(self, message): - if isinstance(message, Message): - return message.as_dict() - else: - return json.JSONEncoder.default(self, message) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index e4fe83cc..9a4258a6 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -18,6 +18,7 @@ from ably.rest.auth import Auth from ably.types.message import Message from ably.types.tokendetails import TokenDetails +from ably.util import case from test.ably.restsetup import RestSetup from test.ably.utils import VaryByProtocolTestsMetaclass, dont_vary_protocol, BaseTestCase @@ -406,12 +407,18 @@ def test_interoperability(self): assert type(message.data) == type_mapping[expected_type] +@six.add_metaclass(VaryByProtocolTestsMetaclass) class TestRestChannelPublishIdempotent(BaseTestCase): - def setUp(self): - self.ably = RestSetup.get_ably_rest() + @classmethod + def setUpClass(cls): + cls.ably = RestSetup.get_ably_rest() + + def per_protocol_setup(self, use_binary_protocol): + self.ably.options.use_binary_protocol = use_binary_protocol # TO3n + @dont_vary_protocol def test_idempotent_rest_publishing(self): # Test default value if api_version < '1.1': @@ -425,3 +432,19 @@ def test_idempotent_rest_publishing(self): ably = RestSetup.get_ably_rest(idempotent_rest_publishing=False) assert ably.options.idempotent_rest_publishing is False + + # RSL1j + @dont_vary_protocol + def test_message_serialization(self): + channel = self.get_channel() + + data = { + 'name': 'name', + 'data': 'data', + 'client_id': 'client_id', + 'extras': {}, + } + message = Message(**data) + request_body = channel._Channel__publish_request_body(messages=[message]) + input_keys = set(case.snake_to_camel(x) for x in data.keys()) + assert input_keys - set(request_body) == set() From 64b7297150c65415acc33a67d72cf73a4d8c5969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 17:24:59 +0200 Subject: [PATCH 56/85] RSL1k1 Idempotent publishing via library-generated ids --- ably/rest/channel.py | 12 +++++++++++- ably/types/message.py | 4 ++++ test/ably/restchannelpublish_test.py | 13 +++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index e75896b6..bc4d9700 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -1,8 +1,10 @@ from __future__ import absolute_import +import base64 +from collections import OrderedDict import logging import json -from collections import OrderedDict +import os import six import msgpack @@ -45,6 +47,14 @@ def __publish_request_body(self, name=None, data=None, client_id=None, if not messages: messages = [Message(name, data, client_id, extras=extras)] + # Idempotent publishing + if self.ably.options.idempotent_rest_publishing: + # RSL1k1 + if all(message.id is None for message in messages): + base_id = base64.b64encode(os.urandom(12)).decode() + for serial, message in enumerate(messages): + message.id = '{}:{}'.format(base_id, serial) + request_body_list = [] for m in messages: if m.client_id == '*': diff --git a/ably/types/message.py b/ably/types/message.py index 94845992..6bf10734 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -77,6 +77,10 @@ def connection_key(self): def id(self): return self.__id + @id.setter + def id(self, value): + self.__id = value + @property def timestamp(self): return self.__timestamp diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 9a4258a6..ee85e31c 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import base64 import binascii import json import logging @@ -413,6 +414,7 @@ class TestRestChannelPublishIdempotent(BaseTestCase): @classmethod def setUpClass(cls): cls.ably = RestSetup.get_ably_rest() + cls.ably_idempotent = RestSetup.get_ably_rest(idempotent_rest_publishing=True) def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol @@ -448,3 +450,14 @@ def test_message_serialization(self): request_body = channel._Channel__publish_request_body(messages=[message]) input_keys = set(case.snake_to_camel(x) for x in data.keys()) assert input_keys - set(request_body) == set() + + # RSL1k1 + @dont_vary_protocol + def test_idempotent_library_generated(self): + channel = self.ably_idempotent.channels[self.get_channel_name()] + + message = Message('name', 'data') + request_body = channel._Channel__publish_request_body(messages=[message]) + base_id, serial = request_body['id'].split(':') + assert len(base64.b64decode(base_id)) >= 9 + assert serial == '0' From 0b63d100eeef5b5f0ef8d0cd08aa040dfb1a92d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 17:28:30 +0200 Subject: [PATCH 57/85] RSL1k2 Idempotent publishing via client-supplied id --- test/ably/restchannelpublish_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index ee85e31c..f53889bb 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -461,3 +461,12 @@ def test_idempotent_library_generated(self): base_id, serial = request_body['id'].split(':') assert len(base64.b64decode(base_id)) >= 9 assert serial == '0' + + # RSL1k2 + @dont_vary_protocol + def test_idempotent_client_supplied(self): + channel = self.ably_idempotent.channels[self.get_channel_name()] + + message = Message('name', 'data', id='foobar') + request_body = channel._Channel__publish_request_body(messages=[message]) + assert request_body['id'] == 'foobar' From 27917b88cb4d8353648f77704099c891a1a53c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 17:34:09 +0200 Subject: [PATCH 58/85] RSL1k3 Idempotent publishing with mixed ids --- test/ably/restchannelpublish_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index f53889bb..0c416dd6 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -470,3 +470,16 @@ def test_idempotent_client_supplied(self): message = Message('name', 'data', id='foobar') request_body = channel._Channel__publish_request_body(messages=[message]) assert request_body['id'] == 'foobar' + + # RSL1k3 + @dont_vary_protocol + def test_idempotent_mixed_ids(self): + channel = self.ably_idempotent.channels[self.get_channel_name()] + + messages = [ + Message('name', 'data', id='foobar'), + Message('name', 'data'), + ] + request_body = channel._Channel__publish_request_body(messages=messages) + assert request_body[0]['id'] == 'foobar' + assert 'id' not in request_body[1] From 23df9346ba29386ef0e88cf5c56570b907269fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 10 Aug 2018 20:01:35 +0200 Subject: [PATCH 59/85] RSL1k4 Idempotency test with library generated ids --- ably/http/http.py | 6 ++---- test/ably/restchannelpublish_test.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index 05c138dd..4aca57b5 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -170,10 +170,8 @@ def make_request(self, method, path, headers=None, body=None, request = requests.Request(method, url, data=body, headers=all_headers) prepped = self.__session.prepare_request(request) try: - response = self.__session.send( - prepped, - timeout=(http_open_timeout, - http_request_timeout)) + timeout = (http_open_timeout, http_request_timeout) + response = self.__session.send(prepped, timeout=timeout) except Exception as e: # Need to catch `Exception`, see: # https://github.com/kennethreitz/requests/issues/1236#issuecomment-133312626 diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 0c416dd6..b9594ae6 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -483,3 +483,28 @@ def test_idempotent_mixed_ids(self): request_body = channel._Channel__publish_request_body(messages=messages) assert request_body[0]['id'] == 'foobar' assert 'id' not in request_body[1] + + # RSL1k4 + def test_idempotent_library_generated_retry(self): + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True) + if not ably.options.fallback_hosts: + host = ably.options.get_rest_host() + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) + channel = ably.channels[self.get_channel_name()] + + failures = 0 + send = requests.sessions.Session.send + def side_effect(self, *args, **kwargs): + nonlocal failures + x = send(self, *args, **kwargs) + if failures < 2: + failures += 1 + raise Exception('faked exception') + return x + + messages = [Message('name1', 'data1')] + with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): + channel.publish(messages=messages) + + assert failures == 2 + assert len(channel.history().items) == 1 From 9298a8307f7237397ef0408b9bdb00dc97306e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 14 Aug 2018 10:22:11 +0200 Subject: [PATCH 60/85] RSL1k5 Idempotency test with client supplied ids --- test/ably/restchannelpublish_test.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index b9594ae6..5f483f15 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -508,3 +508,28 @@ def side_effect(self, *args, **kwargs): assert failures == 2 assert len(channel.history().items) == 1 + + # RSL1k5 + def test_idempotent_client_supplied_retry(self): + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True) + if not ably.options.fallback_hosts: + host = ably.options.get_rest_host() + ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) + channel = ably.channels[self.get_channel_name()] + + failures = 0 + send = requests.sessions.Session.send + def side_effect(self, *args, **kwargs): + nonlocal failures + x = send(self, *args, **kwargs) + if failures < 2: + failures += 1 + raise Exception('faked exception') + return x + + messages = [Message('name1', 'data1', id='foobar')] + with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): + channel.publish(messages=messages) + + assert failures == 2 + assert len(channel.history().items) == 1 From 7bad6317d204d8c1898c6c00b47a912619b6f3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 14 Aug 2018 11:04:23 +0200 Subject: [PATCH 61/85] Fix syntax error with Python 2 --- test/ably/restchannelpublish_test.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 5f483f15..0f2154ca 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -492,13 +492,12 @@ def test_idempotent_library_generated_retry(self): ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] - failures = 0 + state = {'failures': 0} send = requests.sessions.Session.send def side_effect(self, *args, **kwargs): - nonlocal failures x = send(self, *args, **kwargs) - if failures < 2: - failures += 1 + if state['failures'] < 2: + state['failures'] += 1 raise Exception('faked exception') return x @@ -506,7 +505,7 @@ def side_effect(self, *args, **kwargs): with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): channel.publish(messages=messages) - assert failures == 2 + assert state['failures'] == 2 assert len(channel.history().items) == 1 # RSL1k5 @@ -517,13 +516,12 @@ def test_idempotent_client_supplied_retry(self): ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] - failures = 0 + state = {'failures': 0} send = requests.sessions.Session.send def side_effect(self, *args, **kwargs): - nonlocal failures x = send(self, *args, **kwargs) - if failures < 2: - failures += 1 + if state['failures'] < 2: + state['failures'] += 1 raise Exception('faked exception') return x @@ -531,5 +529,5 @@ def side_effect(self, *args, **kwargs): with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): channel.publish(messages=messages) - assert failures == 2 + assert state['failures'] == 2 assert len(channel.history().items) == 1 From 99bf9b0c042b5cdcc7bfe91fc00640183945c1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 17 Aug 2018 10:36:43 +0200 Subject: [PATCH 62/85] RSL1j Test id as well --- test/ably/restchannelpublish_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 0f2154ca..0a1be4fd 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -445,6 +445,7 @@ def test_message_serialization(self): 'data': 'data', 'client_id': 'client_id', 'extras': {}, + 'id': 'foobar', } message = Message(**data) request_body = channel._Channel__publish_request_body(messages=[message]) From 61f232a857e8d7d998bd14a6534fde1be629f3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 17 Aug 2018 11:14:39 +0200 Subject: [PATCH 63/85] Run pytest with log-level debug --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index e2159f5c..f498f3f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,5 @@ branch=True [flake8] max-line-length = 120 ignore = E114,E121,E123,E126,E127,E128,E241,E226,E231,E251,E302,E305,E306,E402,E501,F401,F821,F841,I100,I101,I201,N802,W291,W293,W391,W503 +[tool:pytest] +log_level = DEBUG From ead792b0ce0bfa24b52c5b9257e2ec86695f1de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 20 Aug 2018 16:54:22 +0200 Subject: [PATCH 64/85] Fix testing with text protocol --- test/ably/restchannelpublish_test.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 0a1be4fd..818ec5e4 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -418,6 +418,7 @@ def setUpClass(cls): def per_protocol_setup(self, use_binary_protocol): self.ably.options.use_binary_protocol = use_binary_protocol + self.use_binary_protocol = use_binary_protocol # TO3n @dont_vary_protocol @@ -485,12 +486,16 @@ def test_idempotent_mixed_ids(self): assert request_body[0]['id'] == 'foobar' assert 'id' not in request_body[1] + def get_ably_rest(self, *args, **kwargs): + kwargs['use_binary_protocol'] = self.use_binary_protocol + return RestSetup.get_ably_rest(*args, **kwargs) + # RSL1k4 def test_idempotent_library_generated_retry(self): - ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True) + ably = self.get_ably_rest(idempotent_rest_publishing=True) if not ably.options.fallback_hosts: host = ably.options.get_rest_host() - ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) + ably = self.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] state = {'failures': 0} @@ -511,10 +516,10 @@ def side_effect(self, *args, **kwargs): # RSL1k5 def test_idempotent_client_supplied_retry(self): - ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True) + ably = self.get_ably_rest(idempotent_rest_publishing=True) if not ably.options.fallback_hosts: host = ably.options.get_rest_host() - ably = RestSetup.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) + ably = self.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] state = {'failures': 0} From 7f648c65d6a95efc99d05784175000694d5adebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 20 Aug 2018 16:57:09 +0200 Subject: [PATCH 65/85] tests: print request/response for debugging --- requirements-test.txt | 2 ++ test/ably/__init__.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/requirements-test.txt b/requirements-test.txt index 050c8a5a..0cc378f4 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -12,3 +12,5 @@ pytest-flake8 #pytest-timeout>=1.2.0,<2 pytest-xdist>=1.15.0,<2 responses>=0.5.0,<1.0 + +requests-toolbelt diff --git a/test/ably/__init__.py b/test/ably/__init__.py index e69de29b..e314c78b 100644 --- a/test/ably/__init__.py +++ b/test/ably/__init__.py @@ -0,0 +1,20 @@ +from requests.adapters import HTTPAdapter + +real_send = HTTPAdapter.send +def send(*args, **kw): + response = real_send(*args, **kw) + + from requests_toolbelt.utils import dump + data = dump.dump_all(response) + for line in data.splitlines(): + try: + line = line.decode('utf-8') + except UnicodeDecodeError: + line = bytes(line) + print(line) + + return response + + +# Uncomment this to print request/response +HTTPAdapter.send = send From 5242bac26b67964e39843f5e5a52262744371f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 23 Aug 2018 15:57:18 +0200 Subject: [PATCH 66/85] Publish post to /messages not /publish --- ably/rest/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index bc4d9700..dd58058b 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -101,7 +101,7 @@ def publish(self, name=None, data=None, client_id=None, extras=None, else: request_body = msgpack.packb(request_body, use_bin_type=True) - path = '/channels/%s/publish' % self.__name + path = '/channels/%s/messages' % self.__name return self.ably.http.post(path, body=request_body, timeout=timeout) @property From 3e10acd9088a9b3576715487d30c92dea13032ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 23 Aug 2018 16:01:30 +0200 Subject: [PATCH 67/85] Don't simulate erros in RSL1k5 test --- test/ably/restchannelpublish_test.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 818ec5e4..32bd8eef 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -522,18 +522,8 @@ def test_idempotent_client_supplied_retry(self): ably = self.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] - state = {'failures': 0} - send = requests.sessions.Session.send - def side_effect(self, *args, **kwargs): - x = send(self, *args, **kwargs) - if state['failures'] < 2: - state['failures'] += 1 - raise Exception('faked exception') - return x - messages = [Message('name1', 'data1', id='foobar')] - with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): - channel.publish(messages=messages) - - assert state['failures'] == 2 + channel.publish(messages=messages) + channel.publish(messages=messages) + channel.publish(messages=messages) assert len(channel.history().items) == 1 From 3c94374dd4aaa5bcedaeb00cce73e8a1f02cbce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 24 Aug 2018 09:59:39 +0200 Subject: [PATCH 68/85] Get history from /messages not /history And use RestSetup.get_ably_rest in another place. --- ably/rest/channel.py | 2 +- test/ably/restcrypto_test.py | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index dd58058b..2ca2c821 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -32,7 +32,7 @@ def __init__(self, ably, name, options): def history(self, direction=None, limit=None, start=None, end=None, timeout=None): """Returns the history for this channel""" params = format_params({}, direction=direction, start=start, end=end, limit=limit) - path = '/channels/%s/history' % self.__name + path = '/channels/%s/messages' % self.__name path += params message_handler = make_message_response_handler(self.__cipher) diff --git a/test/ably/restcrypto_test.py b/test/ably/restcrypto_test.py index 3b212999..9921d0e4 100644 --- a/test/ably/restcrypto_test.py +++ b/test/ably/restcrypto_test.py @@ -9,7 +9,6 @@ import six from ably import AblyException -from ably import AblyRest from ably.types.message import Message from ably.util.crypto import CipherParams, get_cipher, generate_random_key, get_default_params @@ -26,15 +25,8 @@ class TestRestCrypto(BaseTestCase): def setUp(self): - options = { - "key": test_vars["keys"][0]["key_str"], - "rest_host": test_vars["host"], - "port": test_vars["port"], - "tls_port": test_vars["tls_port"], - "tls": test_vars["tls"], - } - self.ably = AblyRest(**options) - self.ably2 = AblyRest(**options) + self.ably = RestSetup.get_ably_rest() + self.ably2 = RestSetup.get_ably_rest() def per_protocol_setup(self, use_binary_protocol): # This will be called every test that vary by protocol for each protocol From b85340fc4962fff94bbcef59878f01e59542e53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 24 Aug 2018 10:31:37 +0200 Subject: [PATCH 69/85] Switch tests from sandbox to dev --- test/ably/restsetup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ably/restsetup.py b/test/ably/restsetup.py index debc47a0..0c930a86 100644 --- a/test/ably/restsetup.py +++ b/test/ably/restsetup.py @@ -16,7 +16,7 @@ app_spec_local = json.loads(f.read()) tls = (os.environ.get('ABLY_TLS') or "true").lower() == "true" -host = os.environ.get('ABLY_HOST', 'sandbox-rest.ably.io') +host = os.environ.get('ABLY_HOST', 'dev-rest.ably.io') environment = os.environ.get('ABLY_ENV') port = 80 From 448aab219ad0c5f7721be85d29e4019311195e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 3 Sep 2018 18:02:59 +0200 Subject: [PATCH 70/85] Do not send timestamp in publish --- ably/types/message.py | 4 ++-- test/ably/restchannelpublish_test.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ably/types/message.py b/ably/types/message.py index 6bf10734..5bcc9afa 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -3,7 +3,6 @@ import base64 import json import logging -import time import six @@ -168,8 +167,9 @@ def as_dict(self, binary=False): request_body = { u'name': self.name, u'data': data, - u'timestamp': self.timestamp or int(time.time() * 1000.0), } + if self.timestamp: + request_body[u'timestamp'] = self.timestamp request_body = {k: v for (k, v) in request_body.items() if v is not None} # None values aren't included diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 32bd8eef..af0ec107 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -199,7 +199,6 @@ def test_publish_message_null_name_and_data_keys_arent_sent(self): else: posted_body = json.loads(post_mock.call_args[1]['body']) - assert 'timestamp' in posted_body assert 'name' not in posted_body assert 'data' not in posted_body From 1cc9b9e3aebc64dfb1446cfac610c2d6b116a9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Sep 2018 10:04:35 +0200 Subject: [PATCH 71/85] Fix tests --- ably/rest/channel.py | 2 +- ably/types/message.py | 23 +++++++++++++---------- test/ably/restauth_test.py | 3 +-- test/ably/restchannelhistory_test.py | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index 2ca2c821..b910b5fc 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -53,7 +53,7 @@ def __publish_request_body(self, name=None, data=None, client_id=None, if all(message.id is None for message in messages): base_id = base64.b64encode(os.urandom(12)).decode() for serial, message in enumerate(messages): - message.id = '{}:{}'.format(base_id, serial) + message.id = u'{}:{}'.format(base_id, serial) request_body_list = [] for m in messages: diff --git a/ably/types/message.py b/ably/types/message.py index 5bcc9afa..af31a0e2 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -14,21 +14,24 @@ log = logging.getLogger(__name__) +def to_text(value): + if value is None: + return value + elif isinstance(value, six.text_type): + return value + elif isinstance(value, six.binary_type): + return value.decode('ascii') + else: + raise TypeError("expected string or bytes, not %s" % type(value)) + + class Message(EncodeDataMixin): def __init__(self, name=None, data=None, client_id=None, extras=None, id=None, connection_id=None, connection_key=None, timestamp=None, encoding=''): - if name is None: - self.__name = None - elif isinstance(name, six.text_type): - self.__name = name - elif isinstance(name, six.binary_type): - self.__name = name.decode('ascii') - else: - raise ValueError("name must be a string or bytes, not %s" % type(name)) - - self.__id = id + self.__name = to_text(name) + self.__id = to_text(id) self.__client_id = client_id self.__data = data self.__timestamp = timestamp diff --git a/test/ably/restauth_test.py b/test/ably/restauth_test.py index 796d99c4..1feeaea4 100644 --- a/test/ably/restauth_test.py +++ b/test/ably/restauth_test.py @@ -488,8 +488,7 @@ def call_back(request): responses.add_callback( responses.POST, - 'https://{}:443/channels/{}/publish'.format( - host, self.channel), + 'https://{}:443/channels/{}/messages'.format(host, self.channel), call_back) responses.start() diff --git a/test/ably/restchannelhistory_test.py b/test/ably/restchannelhistory_test.py index f608c2e5..f8aea94c 100644 --- a/test/ably/restchannelhistory_test.py +++ b/test/ably/restchannelhistory_test.py @@ -99,7 +99,7 @@ def history_mock_url(self, channel_name): kwargs['port_sufix'] = '' else: kwargs['port_sufix'] = ':' + str(port) - url = '{scheme}://{host}{port_sufix}/channels/{channel_name}/history' + url = '{scheme}://{host}{port_sufix}/channels/{channel_name}/messages' return url.format(**kwargs) @responses.activate From a5307dc93a60d4fc3757ad63ca4e884cb4223eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Sep 2018 10:05:37 +0200 Subject: [PATCH 72/85] tests: disable debugging --- setup.cfg | 2 +- test/ably/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index f498f3f9..e82e96d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,4 +4,4 @@ branch=True max-line-length = 120 ignore = E114,E121,E123,E126,E127,E128,E241,E226,E231,E251,E302,E305,E306,E402,E501,F401,F821,F841,I100,I101,I201,N802,W291,W293,W391,W503 [tool:pytest] -log_level = DEBUG +#log_level = DEBUG diff --git a/test/ably/__init__.py b/test/ably/__init__.py index e314c78b..2ea6fb48 100644 --- a/test/ably/__init__.py +++ b/test/ably/__init__.py @@ -17,4 +17,4 @@ def send(*args, **kw): # Uncomment this to print request/response -HTTPAdapter.send = send +#HTTPAdapter.send = send From a75999958a5b7bde037bc5eefed3ceb63f8ffd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Sep 2018 10:14:51 +0200 Subject: [PATCH 73/85] Pass flake8 --- test/ably/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ably/__init__.py b/test/ably/__init__.py index 2ea6fb48..0aa32c4a 100644 --- a/test/ably/__init__.py +++ b/test/ably/__init__.py @@ -17,4 +17,4 @@ def send(*args, **kw): # Uncomment this to print request/response -#HTTPAdapter.send = send +# HTTPAdapter.send = send From f17a8e4c330ba160e5a8856e96c8812be42fb5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 11 Sep 2018 12:27:03 +0200 Subject: [PATCH 74/85] Remove unneeded code from RSL1k5 test --- test/ably/restchannelpublish_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index af0ec107..7d0c1ecf 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -514,11 +514,8 @@ def side_effect(self, *args, **kwargs): assert len(channel.history().items) == 1 # RSL1k5 - def test_idempotent_client_supplied_retry(self): + def test_idempotent_client_supplied_publish(self): ably = self.get_ably_rest(idempotent_rest_publishing=True) - if not ably.options.fallback_hosts: - host = ably.options.get_rest_host() - ably = self.get_ably_rest(idempotent_rest_publishing=True, fallback_hosts=[host] * 3) channel = ably.channels[self.get_channel_name()] messages = [Message('name1', 'data1', id='foobar')] From 400cfd040150367bfac3036c107c36d2763d984c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 13 Nov 2018 16:57:34 +0100 Subject: [PATCH 75/85] Fix tests, use idempotent-dev --- test/ably/restsetup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ably/restsetup.py b/test/ably/restsetup.py index 0c930a86..499db32c 100644 --- a/test/ably/restsetup.py +++ b/test/ably/restsetup.py @@ -16,7 +16,7 @@ app_spec_local = json.loads(f.read()) tls = (os.environ.get('ABLY_TLS') or "true").lower() == "true" -host = os.environ.get('ABLY_HOST', 'dev-rest.ably.io') +host = os.environ.get('ABLY_HOST', 'idempotent-dev-rest.ably.io') environment = os.environ.get('ABLY_ENV') port = 80 From 436b2b98bafacde6ebf08fdc4bbfdc44f870080e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 13 Nov 2018 17:53:05 +0100 Subject: [PATCH 76/85] Handle forward slash in channel name Fixes #130 --- ably/rest/channel.py | 9 ++++----- ably/types/presence.py | 10 +++++----- test/ably/restchannelpublish_test.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ably/rest/channel.py b/ably/rest/channel.py index b910b5fc..99903d19 100644 --- a/ably/rest/channel.py +++ b/ably/rest/channel.py @@ -8,7 +8,7 @@ import six import msgpack -from six.moves.urllib.parse import quote +from six.moves.urllib import parse from ably.http.paginatedresult import PaginatedResult, format_params from ably.types.message import Message, make_message_response_handler @@ -23,7 +23,7 @@ class Channel(object): def __init__(self, ably, name, options): self.__ably = ably self.__name = name - self.__base_path = '/channels/%s/' % quote(name) + self.__base_path = '/channels/%s/' % parse.quote_plus(name, safe=':') self.__cipher = None self.options = options self.__presence = Presence(self) @@ -32,8 +32,7 @@ def __init__(self, ably, name, options): def history(self, direction=None, limit=None, start=None, end=None, timeout=None): """Returns the history for this channel""" params = format_params({}, direction=direction, start=start, end=end, limit=limit) - path = '/channels/%s/messages' % self.__name - path += params + path = self.__base_path + 'messages' + params message_handler = make_message_response_handler(self.__cipher) return PaginatedResult.paginated_query( @@ -101,7 +100,7 @@ def publish(self, name=None, data=None, client_id=None, extras=None, else: request_body = msgpack.packb(request_body, use_bin_type=True) - path = '/channels/%s/messages' % self.__name + path = self.__base_path + 'messages' return self.ably.http.post(path, body=request_body, timeout=timeout) @property diff --git a/ably/types/presence.py b/ably/types/presence.py index a407dc7a..97ed53e8 100644 --- a/ably/types/presence.py +++ b/ably/types/presence.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta -from six.moves.urllib.parse import urlencode +from six.moves.urllib import parse from ably.http.paginatedresult import PaginatedResult from ably.types.mixins import EncodeDataMixin @@ -101,7 +101,7 @@ def timestamp(self): class Presence(object): def __init__(self, channel): - self.__base_path = channel.base_path + self.__base_path = '/channels/%s/' % parse.quote_plus(channel.name) self.__binary = channel.ably.options.use_binary_protocol self.__http = channel.ably.http self.__cipher = channel.cipher @@ -109,7 +109,7 @@ def __init__(self, channel): def _path_with_qs(self, rel_path, qs=None): path = rel_path if qs: - path += ('?' + urlencode(qs)) + path += ('?' + parse.urlencode(qs)) return path def get(self, limit=None): @@ -118,7 +118,7 @@ def get(self, limit=None): if limit > 1000: raise ValueError("The maximum allowed limit is 1000") qs['limit'] = limit - path = self._path_with_qs('%s/presence' % self.__base_path.rstrip('/'), qs) + path = self._path_with_qs(self.__base_path + 'presence', qs) presence_handler = make_presence_response_handler(self.__cipher) return PaginatedResult.paginated_query( @@ -146,7 +146,7 @@ def history(self, limit=None, direction=None, start=None, end=None): if 'start' in qs and 'end' in qs and qs['start'] > qs['end']: raise ValueError("'end' parameter has to be greater than or equal to 'start'") - path = self._path_with_qs('%s/presence/history' % self.__base_path.rstrip('/'), qs) + path = self._path_with_qs(self.__base_path + 'presence/history', qs) presence_handler = make_presence_response_handler(self.__cipher) return PaginatedResult.paginated_query( diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 7d0c1ecf..76ec0359 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -406,6 +406,16 @@ def test_interoperability(self): assert message.data == expected_value assert type(message.data) == type_mapping[expected_type] + # https://github.com/ably/ably-python/issues/130 + def test_publish_slash(self): + channel = self.ably.channels.get(self.get_channel_name('persisted:widgets/')) + name, data = 'Name', 'Data' + channel.publish(name, data) + history = channel.history().items + assert len(history) == 1 + assert history[0].name == name + assert history[0].data == data + @six.add_metaclass(VaryByProtocolTestsMetaclass) class TestRestChannelPublishIdempotent(BaseTestCase): From b7c3e19a8085ec3fa2bcf85ebf0e6c5bdcd073df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 29 Nov 2018 12:49:39 +0100 Subject: [PATCH 77/85] Share session accross Ably clients Fixes #133 --- ably/http/http.py | 3 ++- ably/rest/rest.py | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index 4aca57b5..32504639 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -109,12 +109,13 @@ class Http(object): 'http_max_retry_duration': 15, } + __session = requests.Session() + def __init__(self, ably, options): options = options or {} self.__ably = ably self.__options = options - self.__session = requests.Session() self.__auth = None def dump_body(self, body): diff --git a/ably/rest/rest.py b/ably/rest/rest.py index 915a5fc1..4415428c 100644 --- a/ably/rest/rest.py +++ b/ably/rest/rest.py @@ -65,11 +65,6 @@ def __init__(self, key=None, token=None, token_details=None, **kwargs): else: options = Options(**kwargs) - # if self.__keep_alive: - # self.__session = requests.Session() - # else: - # self.__session = None - self.__http = Http(self, options) self.__auth = Auth(self, options) self.__http.auth = self.__auth From 3df1c286e53f0ae972ced781c3ad419c95b9349b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 29 Nov 2018 12:59:04 +0100 Subject: [PATCH 78/85] Idempotent only enabled in 1.2 Fixes #132 --- ably/types/options.py | 2 +- test/ably/restchannelpublish_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/types/options.py b/ably/types/options.py index af336099..c4a2a92c 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -24,7 +24,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, if idempotent_rest_publishing is None: from ably import api_version - idempotent_rest_publishing = api_version >= '1.1' + idempotent_rest_publishing = api_version >= '1.2' self.__client_id = client_id self.__log_level = log_level diff --git a/test/ably/restchannelpublish_test.py b/test/ably/restchannelpublish_test.py index 76ec0359..69b55c99 100644 --- a/test/ably/restchannelpublish_test.py +++ b/test/ably/restchannelpublish_test.py @@ -433,7 +433,7 @@ def per_protocol_setup(self, use_binary_protocol): @dont_vary_protocol def test_idempotent_rest_publishing(self): # Test default value - if api_version < '1.1': + if api_version < '1.2': assert self.ably.options.idempotent_rest_publishing is False else: assert self.ably.options.idempotent_rest_publishing is True From af82204e8e0f12dbce1ff4a02a994225077be478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 6 Dec 2018 11:17:56 +0100 Subject: [PATCH 79/85] Add patch Fixes #128 --- ably/http/http.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index 32504639..2061ca04 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -191,8 +191,17 @@ def make_request(self, method, path, headers=None, body=None, if not e.is_server_error: raise e + def delete(self, url, headers=None, skip_auth=False, timeout=None): + return self.make_request('DELETE', url, headers=headers, + skip_auth=skip_auth, timeout=timeout) + def get(self, url, headers=None, skip_auth=False, timeout=None): - return self.make_request('GET', url, headers=headers, skip_auth=skip_auth, timeout=timeout) + return self.make_request('GET', url, headers=headers, + skip_auth=skip_auth, timeout=timeout) + + def patch(self, url, headers=None, body=None, skip_auth=False, timeout=None): + return self.make_request('PATCH', url, headers=headers, body=body, + skip_auth=skip_auth, timeout=timeout) def post(self, url, headers=None, body=None, skip_auth=False, timeout=None): return self.make_request('POST', url, headers=headers, body=body, @@ -202,9 +211,6 @@ def put(self, url, headers=None, body=None, skip_auth=False, timeout=None): return self.make_request('PUT', url, headers=headers, body=body, skip_auth=skip_auth, timeout=timeout) - def delete(self, url, headers=None, skip_auth=False, timeout=None): - return self.make_request('DELETE', url, headers=headers, skip_auth=skip_auth, timeout=timeout) - @property def auth(self): return self.__auth From 3e51c5ea4d2fe06e79ecbb4a2ee6277e7113bab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 25 Jan 2019 14:19:54 +0100 Subject: [PATCH 80/85] RSC15f Support for remembered REST fallback host Fixes issue #131 --- ably/http/http.py | 32 +++++++++++++++++++++++++++----- ably/transport/defaults.py | 2 ++ ably/types/options.py | 9 ++++++++- test/ably/resthttp_test.py | 29 ++++++++++++++++++++++++++++- test/ably/restinit_test.py | 6 ++++++ 5 files changed, 71 insertions(+), 7 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index 2061ca04..fc295bb5 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -115,8 +115,10 @@ def __init__(self, ably, options): options = options or {} self.__ably = ably self.__options = options - self.__auth = None + # Cached fallback host (RSC15f) + self.__host = None + self.__host_expires = None def dump_body(self, body): if self.options.use_binary_protocol: @@ -133,6 +135,22 @@ def reauth(self): " no means to generate a new token") raise e + def get_rest_hosts(self): + hosts = self.options.get_rest_hosts() + host = self.__host + if host is None: + return hosts + + if time.time() > self.__host_expires: + self.__host = None + self.__host_expires = None + return hosts + + hosts = list(hosts) + hosts.remove(host) + hosts.insert(0, host) + return hosts + @reauth_if_expired def make_request(self, method, path, headers=None, body=None, skip_auth=False, timeout=None, raise_on_error=True): @@ -157,12 +175,11 @@ def make_request(self, method, path, headers=None, body=None, if headers: all_headers.update(headers) - http_open_timeout = self.http_open_timeout - http_request_timeout = self.http_request_timeout + timeout = (self.http_open_timeout, self.http_request_timeout) http_max_retry_duration = self.http_max_retry_duration requested_at = time.time() - hosts = self.options.get_rest_hosts() + hosts = self.get_rest_hosts() for retry_count, host in enumerate(hosts): base_url = "%s://%s:%d" % (self.preferred_scheme, host, @@ -171,7 +188,6 @@ def make_request(self, method, path, headers=None, body=None, request = requests.Request(method, url, data=body, headers=all_headers) prepped = self.__session.prepare_request(request) try: - timeout = (http_open_timeout, http_request_timeout) response = self.__session.send(prepped, timeout=timeout) except Exception as e: # Need to catch `Exception`, see: @@ -186,6 +202,12 @@ def make_request(self, method, path, headers=None, body=None, try: if raise_on_error: AblyException.raise_for_response(response) + + # Keep fallback host for later (RSC15f) + if retry_count > 0 and host != self.options.get_rest_host(): + self.__host = host + self.__host_expires = time.time() + (self.options.fallback_retry_timeout / 1000) + return Response(response) except AblyException as e: if not e.is_server_error: diff --git a/ably/transport/defaults.py b/ably/transport/defaults.py index d577bc25..7d1273c7 100644 --- a/ably/transport/defaults.py +++ b/ably/transport/defaults.py @@ -27,6 +27,8 @@ class Defaults(object): http_max_retry_count = 3 + fallback_retry_timeout = 600000 # 10min + @staticmethod def get_port(options): if options.tls: diff --git a/ably/types/options.py b/ably/types/options.py index c4a2a92c..b0522333 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -12,12 +12,14 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, queue_messages=False, recover=False, environment=None, http_open_timeout=None, http_request_timeout=None, http_max_retry_count=None, http_max_retry_duration=None, - fallback_hosts=None, fallback_hosts_use_default=None, + fallback_hosts=None, fallback_hosts_use_default=None, fallback_retry_timeout=None, idempotent_rest_publishing=None, **kwargs): super(Options, self).__init__(**kwargs) # TODO check these defaults + if fallback_retry_timeout is None: + fallback_retry_timeout = Defaults.fallback_retry_timeout if environment is not None and rest_host is not None: raise ValueError('specify rest_host or environment, not both') @@ -43,6 +45,7 @@ def __init__(self, client_id=None, log_level=0, tls=True, rest_host=None, self.__http_max_retry_duration = http_max_retry_duration self.__fallback_hosts = fallback_hosts self.__fallback_hosts_use_default = fallback_hosts_use_default + self.__fallback_retry_timeout = fallback_retry_timeout self.__idempotent_rest_publishing = idempotent_rest_publishing self.__rest_hosts = self.__get_rest_hosts() @@ -171,6 +174,10 @@ def fallback_hosts(self): def fallback_hosts_use_default(self): return self.__fallback_hosts_use_default + @property + def fallback_retry_timeout(self): + return self.__fallback_retry_timeout + @property def idempotent_rest_publishing(self): return self.__idempotent_rest_publishing diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index 1970f821..fbe9a84b 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -6,7 +6,7 @@ import mock import pytest import requests -from six.moves.urllib.parse import urljoin +from six.moves.urllib.parse import urljoin, urlparse from ably import AblyRest from ably.transport.defaults import Defaults @@ -94,6 +94,33 @@ def test_no_host_fallback_nor_retries_if_custom_host(self): assert send_mock.call_count == 1 assert request_mock.call_args == mock.call(mock.ANY, custom_url, data=mock.ANY, headers=mock.ANY) + # RSC15f + def test_cached_fallback(self): + ably = RestSetup.get_ably_rest(fallback_hosts_use_default=True, fallback_retry_timeout=100) + host = ably.options.get_rest_host() + + state = {'errors': 0} + send = requests.sessions.Session.send + def side_effect(self, prepped, *args, **kwargs): + if urlparse(prepped.url).hostname == host: + state['errors'] += 1 + raise RuntimeError + return send(self, prepped, *args, **kwargs) + + with mock.patch('requests.sessions.Session.send', side_effect=side_effect, autospec=True): + # The main host is called and there's an error + ably.time() + assert state['errors'] == 1 + + # The cached host is used: no error + ably.time() + assert state['errors'] == 1 + + # The cached host has expired, we've an error again + time.sleep(0.1) + ably.time() + assert state['errors'] == 2 + def test_no_retry_if_not_500_to_599_http_code(self): default_host = Options().get_rest_host() ably = AblyRest(token="foo") diff --git a/test/ably/restinit_test.py b/test/ably/restinit_test.py index 3218efef..e9fdf546 100644 --- a/test/ably/restinit_test.py +++ b/test/ably/restinit_test.py @@ -113,6 +113,12 @@ def test_fallback_hosts(self): http_max_retry_count=10) assert sorted(Defaults.fallback_hosts) == sorted(ably.options.get_fallback_rest_hosts()) + # RSC15f + ably = AblyRest(token='foo') + assert 600000 == ably.options.fallback_retry_timeout + ably = AblyRest(token='foo', fallback_retry_timeout=1000) + assert 1000 == ably.options.fallback_retry_timeout + @dont_vary_protocol def test_specified_realtime_host(self): ably = AblyRest(token='foo', realtime_host="some.other.host") From 4f12e52ae34e77952d9acacfb8b261182f7111a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 25 Jan 2019 16:26:49 +0100 Subject: [PATCH 81/85] Fix for Python 2.7 --- ably/http/http.py | 2 +- ably/transport/defaults.py | 2 +- setup.cfg | 2 +- test/ably/resthttp_test.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ably/http/http.py b/ably/http/http.py index fc295bb5..b4dae5f8 100644 --- a/ably/http/http.py +++ b/ably/http/http.py @@ -206,7 +206,7 @@ def make_request(self, method, path, headers=None, body=None, # Keep fallback host for later (RSC15f) if retry_count > 0 and host != self.options.get_rest_host(): self.__host = host - self.__host_expires = time.time() + (self.options.fallback_retry_timeout / 1000) + self.__host_expires = time.time() + (self.options.fallback_retry_timeout / 1000.0) return Response(response) except AblyException as e: diff --git a/ably/transport/defaults.py b/ably/transport/defaults.py index 7d1273c7..83bf9dca 100644 --- a/ably/transport/defaults.py +++ b/ably/transport/defaults.py @@ -27,7 +27,7 @@ class Defaults(object): http_max_retry_count = 3 - fallback_retry_timeout = 600000 # 10min + fallback_retry_timeout = 600000 # 10min @staticmethod def get_port(options): diff --git a/setup.cfg b/setup.cfg index e82e96d3..b2a36bfa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,6 @@ branch=True [flake8] max-line-length = 120 -ignore = E114,E121,E123,E126,E127,E128,E241,E226,E231,E251,E302,E305,E306,E402,E501,F401,F821,F841,I100,I101,I201,N802,W291,W293,W391,W503 +ignore = E114,E121,E123,E126,E127,E128,E241,E226,E231,E251,E302,E305,E306,E402,E501,F401,F821,F841,I100,I101,I201,N802,W291,W293,W391,W503,W504 [tool:pytest] #log_level = DEBUG diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index fbe9a84b..cc55792d 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -96,7 +96,8 @@ def test_no_host_fallback_nor_retries_if_custom_host(self): # RSC15f def test_cached_fallback(self): - ably = RestSetup.get_ably_rest(fallback_hosts_use_default=True, fallback_retry_timeout=100) + timeout = 100 + ably = RestSetup.get_ably_rest(fallback_hosts_use_default=True, fallback_retry_timeout=timeout) host = ably.options.get_rest_host() state = {'errors': 0} @@ -117,7 +118,7 @@ def side_effect(self, prepped, *args, **kwargs): assert state['errors'] == 1 # The cached host has expired, we've an error again - time.sleep(0.1) + time.sleep(timeout / 1000.0) ably.time() assert state['errors'] == 2 From 96e5d71b2eb25ae1c1dfb46e2e0cb2540b031615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 28 Jan 2019 09:52:58 +0100 Subject: [PATCH 82/85] RSC15f tests, try three times before timeout --- test/ably/resthttp_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index cc55792d..2cd46b86 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -96,7 +96,7 @@ def test_no_host_fallback_nor_retries_if_custom_host(self): # RSC15f def test_cached_fallback(self): - timeout = 100 + timeout = 2000 ably = RestSetup.get_ably_rest(fallback_hosts_use_default=True, fallback_retry_timeout=timeout) host = ably.options.get_rest_host() @@ -115,6 +115,8 @@ def side_effect(self, prepped, *args, **kwargs): # The cached host is used: no error ably.time() + ably.time() + ably.time() assert state['errors'] == 1 # The cached host has expired, we've an error again From 7426c80627de8f669b63e4d96dd693732ddaeff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 2 Feb 2019 11:38:34 +0100 Subject: [PATCH 83/85] Fix flake8 --- ably/types/message.py | 4 ++-- ably/types/options.py | 4 ++-- test/ably/resthttp_test.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ably/types/message.py b/ably/types/message.py index af31a0e2..767f8491 100644 --- a/ably/types/message.py +++ b/ably/types/message.py @@ -147,8 +147,8 @@ def as_dict(self, binary=False): (isinstance(data, bytearray) or # bytearray is always bytes isinstance(data, six.binary_type))): - # at this point binary_type is either a py3k bytes or a py2 - # str that failed to decode to unicode + # at this point binary_type is either a py3k bytes or a py2 + # str that failed to decode to unicode data = base64.b64encode(data).decode('ascii') encoding.append('base64') elif isinstance(data, CipherData): diff --git a/ably/types/options.py b/ably/types/options.py index b0522333..c4c4047f 100644 --- a/ably/types/options.py +++ b/ably/types/options.py @@ -136,7 +136,7 @@ def environment(self): @property def http_open_timeout(self): - return self.__http_open_timeout + return self.__http_open_timeout @http_open_timeout.setter def http_open_timeout(self, value): @@ -144,7 +144,7 @@ def http_open_timeout(self, value): @property def http_request_timeout(self): - return self.__http_request_timeout + return self.__http_request_timeout @http_request_timeout.setter def http_request_timeout(self, value): diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index 2cd46b86..5f6840be 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -72,7 +72,7 @@ def make_url(host): make_url(host) for host in Options(http_max_retry_count=10).get_rest_hosts() ]) - for ((__, url), ___) in request_mock.call_args_list: + for ((_, url), _) in request_mock.call_args_list: assert url in expected_urls_set expected_urls_set.remove(url) From 067ce48dac3c384cd2ea4b5acc88d3604a817da0 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Wed, 13 Feb 2019 15:43:38 +0000 Subject: [PATCH 84/85] Add release badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac555b14..bf5e1880 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ably-python ----------- +[![PyPI version](https://badge.fury.io/py/ably.svg)](https://badge.fury.io/py/ably) [![Coverage Status](https://coveralls.io/repos/ably/ably-python/badge.svg?branch=master&service=github)](https://coveralls.io/github/ably/ably-python?branch=master) A Python client library for [www.ably.io](https://www.ably.io), the realtime messaging service. From 93da4bd79b6957d19daa8415e02b8cfbfda6f230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 13 Feb 2019 18:42:02 +0100 Subject: [PATCH 85/85] v1.1.0 version release --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++- README.md | 11 +++++----- ably/__init__.py | 4 ++-- setup.py | 3 +-- test/ably/resthttp_test.py | 6 +++--- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed370f16..317ddeb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,52 @@ # Change Log -## [v1.0.3](https://github.com/ably/ably-python/tree/v1.0.3) +## [v1.1.0](https://github.com/ably/ably-python/tree/v1.1.0) +[Full Changelog](https://github.com/ably/ably-python/compare/v1.0.3...v1.1.0) +**Closed issues:** + +- Idempotent publishing is not enabled in the upcoming 1.1 release [\#132](https://github.com/ably/ably-python/issues/132) +- forward slash in channel name [\#130](https://github.com/ably/ably-python/issues/130) +- Refactor tests setup [\#109](https://github.com/ably/ably-python/issues/109) + +**Implemented enhancements:** + +- Add support for remembered REST fallback host [\#131](https://github.com/ably/ably-python/issues/131) +- Ensure request method accepts UPDATE, PATCH & DELETE verbs [\#128](https://github.com/ably/ably-python/issues/128) +- Add idempotent REST publishing support [\#121](https://github.com/ably/ably-python/issues/121) +- Allow to configure logger [\#107](https://github.com/ably/ably-python/issues/107) + +**Merged pull requests:** + +- Fix flake8 [\#142](https://github.com/ably/ably-python/pull/142) ([jdavid](https://github.com/jdavid)) +- Rsc15f Support for remembered REST fallback host [\#141](https://github.com/ably/ably-python/pull/141) ([jdavid](https://github.com/jdavid)) +- Add patch [\#135](https://github.com/ably/ably-python/pull/135) ([jdavid](https://github.com/jdavid)) +- Idempotent publishing [\#129](https://github.com/ably/ably-python/pull/129) ([jdavid](https://github.com/jdavid)) +- Push [\#127](https://github.com/ably/ably-python/pull/127) ([jdavid](https://github.com/jdavid)) +- RSH1c5 New push.admin.channel\_subscriptions.remove\_where [\#126](https://github.com/ably/ably-python/pull/126) ([jdavid](https://github.com/jdavid)) +- RSH1c4 New push.admin.channel\_subscriptions.remove [\#125](https://github.com/ably/ably-python/pull/125) ([jdavid](https://github.com/jdavid)) +- RSH1c2 New push.admin.channel\_subscriptions.list\_channels [\#124](https://github.com/ably/ably-python/pull/124) ([jdavid](https://github.com/jdavid)) +- RSH1c1 New push.admin.channel\_subscriptions.list [\#120](https://github.com/ably/ably-python/pull/120) ([jdavid](https://github.com/jdavid)) +- RSH1c3 New push.admin.channel\_subscriptions.save [\#118](https://github.com/ably/ably-python/pull/118) ([jdavid](https://github.com/jdavid)) +- RHS1b5 New push.admin.device\_registrations.remove\_where [\#117](https://github.com/ably/ably-python/pull/117) ([jdavid](https://github.com/jdavid)) +- RHS1b4 New push.admin.device\_registrations.remove [\#116](https://github.com/ably/ably-python/pull/116) ([jdavid](https://github.com/jdavid)) +- RSH1b2 New push.admin.device\_registrations.list [\#114](https://github.com/ably/ably-python/pull/114) ([jdavid](https://github.com/jdavid)) +- Rsh1b1 New push.admin.device\_registrations.get [\#113](https://github.com/ably/ably-python/pull/113) ([jdavid](https://github.com/jdavid)) +- RSH1b3 New push.admin.device\_registrations.save [\#112](https://github.com/ably/ably-python/pull/112) ([jdavid](https://github.com/jdavid)) +- Document how to configure logging [\#110](https://github.com/ably/ably-python/pull/110) ([jdavid](https://github.com/jdavid)) +- Rsh1a New push.admin.publish [\#106](https://github.com/ably/ably-python/pull/106) ([jdavid](https://github.com/jdavid)) + +## [v1.0.3](https://github.com/ably/ably-python/tree/v1.0.3) (2019-01-18) [Full Changelog](https://github.com/ably/ably-python/compare/v1.0.2...v1.0.3) **Closed issues:** - Travis failures with Python 2 in the 1.0 branch [\#138](https://github.com/ably/ably-python/issues/138) +**Fixed bugs:** + +- Authentication with auth\_url doesn't accept camel case [\#136](https://github.com/ably/ably-python/issues/136) + **Merged pull requests:** - clientId must be a \(text\) string [\#139](https://github.com/ably/ably-python/pull/139) ([jdavid](https://github.com/jdavid)) diff --git a/README.md b/README.md index 3a9b315f..b84460d9 100644 --- a/README.md +++ b/README.md @@ -178,11 +178,12 @@ pytest test ## Release Process 1. Update [`setup.py`](./setup.py) and [`ably/__init__.py`](./ably/__init__.py) with the new version number -2. Run `python setup.py sdist upload -r ably` to build and upload this new package to PyPi -3. Run [`github_changelog_generator`](https://github.com/skywinder/Github-Changelog-Generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). Once the CHANGELOG has completed, manually change the `Unreleased` heading and link with the current version number such as `v1.0.0`. Also ensure that the `Full Changelog` link points to the new version tag instead of the `HEAD`. Commit this change. -4. Tag the new version such as `git tag v1.0.0` -5. Visit https://github.com/ably/ably-python/tags and add release notes for the release including links to the changelog entry. -6. Push the tag to origin `git push origin v1.0.0` +2. Run [`github_changelog_generator`](https://github.com/skywinder/Github-Changelog-Generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). Once the CHANGELOG has completed, manually change the `Unreleased` heading and link with the current version number such as `v1.0.0`. Also ensure that the `Full Changelog` link points to the new version tag instead of the `HEAD`. +3. Commit +4. Run `python setup.py sdist upload -r ably` to build and upload this new package to PyPi +5. Tag the new version such as `git tag v1.0.0` +6. Visit https://github.com/ably/ably-python/tags and add release notes for the release including links to the changelog entry. +7. Push the tag to origin `git push origin v1.0.0` ## License diff --git a/ably/__init__.py b/ably/__init__.py index 89ed18f1..a7a5e424 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -29,5 +29,5 @@ def createLock(self): from ably.util.crypto import CipherParams from ably.util.exceptions import AblyException, AblyAuthException, IncompatibleClientIdException -api_version = '1.0' -lib_version = '1.0.3' +api_version = '1.1' +lib_version = '1.1.0' diff --git a/setup.py b/setup.py index a5a6671b..bac8a50c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='ably', - version='1.0.3', + version='1.1.0', classifiers=[ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', @@ -15,7 +15,6 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', diff --git a/test/ably/resthttp_test.py b/test/ably/resthttp_test.py index 5f6840be..9fb48d74 100644 --- a/test/ably/resthttp_test.py +++ b/test/ably/resthttp_test.py @@ -164,15 +164,15 @@ def test_request_headers(self): # API assert 'X-Ably-Version' in r.request.headers - assert r.request.headers['X-Ably-Version'] == '1.0' + assert r.request.headers['X-Ably-Version'] == '1.1' # Lib assert 'X-Ably-Lib' in r.request.headers - expr = r"^python-1\.0\.\d+(-\w+)?$" + expr = r"^python-1\.1\.\d+(-\w+)?$" assert re.search(expr, r.request.headers['X-Ably-Lib']) # Lib Variant ably.set_variant('django') r = ably.http.make_request('HEAD', '/time', skip_auth=True) - expr = r"^python.django-1\.0\.\d+(-\w+)?$" + expr = r"^python.django-1\.1\.\d+(-\w+)?$" assert re.search(expr, r.request.headers['X-Ably-Lib'])