From 6505a900f02e84dbc441d77767e7b47a0be276fb Mon Sep 17 00:00:00 2001 From: Ondrej Kohout Date: Wed, 1 May 2019 08:03:57 +0200 Subject: [PATCH 1/5] Formatting changes - apply black and isort --- README.rst | 2 +- docs/conf.py | 74 +++-- docs/streaming-api-client.rst | 8 +- nameko_salesforce/api/client.py | 25 +- nameko_salesforce/api/dependency.py | 23 +- nameko_salesforce/api/push_topics.py | 78 +++-- nameko_salesforce/constants.py | 16 +- nameko_salesforce/streaming/channels.py | 2 +- nameko_salesforce/streaming/client.py | 90 +++--- setup.py | 36 +-- tests/api/test_client.py | 109 +++---- tests/api/test_dependency.py | 89 ++---- tests/api/test_push_topics.py | 389 ++++++++++------------- tests/conftest.py | 12 +- tests/streaming/conftest.py | 9 +- tests/streaming/test_end_to_end.py | 394 +++++++++++------------- tests/streaming/test_unit.py | 313 ++++++++++--------- 17 files changed, 763 insertions(+), 906 deletions(-) diff --git a/README.rst b/README.rst index ceaa038..d1afec8 100644 --- a/README.rst +++ b/README.rst @@ -56,7 +56,7 @@ has an RPC endpoint for creating new Contact objects in Salesforce: @handle_sobject_notification('Contact', exclude_current_user=False) def handle_contact_updates( self, sobject_type, record_type, notification - ): + ): """ Handle Salesforce contacts updates """ print(notification) diff --git a/docs/conf.py b/docs/conf.py index c318b29..f62e69c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import sphinx_rtd_theme + + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -31,36 +34,38 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.viewcode", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['.templates'] +templates_path = [".templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Nameko Salesforce' -copyright = '2017, Student.com' -author = 'Student.com' +project = "Nameko Salesforce" +copyright = "2017, Student.com" +author = "Student.com" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '' +version = "" # The full version, including alpha/beta/rc tags. -release = '' +release = "" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -72,10 +77,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['.build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [".build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True @@ -86,28 +91,26 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' +html_theme = "alabaster" +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} - -import sphinx_rtd_theme html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.static'] +html_static_path = [".static"] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'NamekoSalesforcedoc' +htmlhelp_basename = "NamekoSalesforcedoc" # -- Options for LaTeX output --------------------------------------------- @@ -116,15 +119,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -134,8 +134,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'NamekoSalesforce.tex', 'Nameko Salesforce Documentation', - 'Student.com', 'manual'), + ( + master_doc, + "NamekoSalesforce.tex", + "Nameko Salesforce Documentation", + "Student.com", + "manual", + ) ] @@ -144,8 +149,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'namekosalesforce', 'Nameko Salesforce Documentation', - [author], 1) + (master_doc, "namekosalesforce", "Nameko Salesforce Documentation", [author], 1) ] @@ -155,13 +159,17 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'NamekoSalesforce', 'Nameko Salesforce Documentation', - author, 'NamekoSalesforce', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "NamekoSalesforce", + "Nameko Salesforce Documentation", + author, + "NamekoSalesforce", + "One line description of project.", + "Miscellaneous", + ) ] - - # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/docs/streaming-api-client.rst b/docs/streaming-api-client.rst index 823e2d4..4ba144d 100644 --- a/docs/streaming-api-client.rst +++ b/docs/streaming-api-client.rst @@ -98,7 +98,7 @@ There are more options available for defining Push Topics: notify_for_operation_delete=False) def handle_contact_updates(self, name, notification): """ Handle Salesforce contacts name changes - + Handles only first and last name changes of existing contacts. Ignores any other modification. @@ -145,7 +145,7 @@ Declaring notification of Salesforce object changes: @handle_sobject_notification('Contact') def handle_contact_updates( self, sobject_type, record_type, notification - ): + ): """ Handle Salesforce contacts updates """ @@ -189,10 +189,10 @@ The following example shows available notification options: self, sobject_type, record_type, notification ): """ Handle Salesforce student contacts name changes - + Handles only name changes of existing contacts of type of student. Ignores any other modification. - + Also ignores changes done by this service (more precisely changes done by the same API user as this extension use for connection to Salesforce streaming API). diff --git a/nameko_salesforce/api/client.py b/nameko_salesforce/api/client.py index 8d647a0..a4a9bfb 100644 --- a/nameko_salesforce/api/client.py +++ b/nameko_salesforce/api/client.py @@ -46,9 +46,7 @@ def __init__(self, pool, get_method_ref): self.pool = pool self.get_method_ref = get_method_ref - @retry( - max_attempts=None, - for_exceptions=simple_salesforce.SalesforceExpiredSession) + @retry(max_attempts=None, for_exceptions=simple_salesforce.SalesforceExpiredSession) def __call__(self, *args, **kwargs): with self.pool.get() as client: @@ -57,7 +55,7 @@ def __call__(self, *args, **kwargs): return method(*args, **kwargs) except ( simple_salesforce.SalesforceExpiredSession, - requests.exceptions.ConnectionError + requests.exceptions.ConnectionError, ): self.pool.discard(client) raise @@ -81,7 +79,6 @@ def __init__(self, attr_name, *args): super().__init__(*args) def __getattr__(self, name): - def get_method_ref(client): attr = getattr(client, self.attr_name) return getattr(attr, name) @@ -104,13 +101,10 @@ def __init__(self, pool): self.pool = pool def __getattr__(self, name): - def get_method_ref(client): return getattr(client, name) - return ClientAttributeProxy( - name, self.pool, get_method_ref - ) + return ClientAttributeProxy(name, self.pool, get_method_ref) class ClientPool(object): @@ -122,8 +116,7 @@ class ClientPool(object): """ def __init__( - self, username, password, security_token, - sandbox=False, api_version=None + self, username, password, security_token, sandbox=False, api_version=None ): self.username = username self.password = password @@ -154,13 +147,11 @@ def create(self): session = requests.Session() retry_adapter = requests.adapters.HTTPAdapter( max_retries=Retry( - connect=CONNECT_RETRIES, - read=READ_RETRIES, - redirect=REDIRECT_RETRIES + connect=CONNECT_RETRIES, read=READ_RETRIES, redirect=REDIRECT_RETRIES ) ) - session.mount('http://', retry_adapter) - session.mount('https://', retry_adapter) + session.mount("http://", retry_adapter) + session.mount("https://", retry_adapter) client = simple_salesforce.Salesforce( username=self.username, @@ -168,7 +159,7 @@ def create(self): security_token=self.security_token, sandbox=self.sandbox, version=self.api_version, - session=session + session=session, ) return client diff --git a/nameko_salesforce/api/dependency.py b/nameko_salesforce/api/dependency.py index c20cdb4..c59eb82 100644 --- a/nameko_salesforce/api/dependency.py +++ b/nameko_salesforce/api/dependency.py @@ -6,6 +6,8 @@ class SalesforceAPI(DependencyProvider): + def __init__(self): + self.client = None def setup(self): @@ -13,24 +15,25 @@ def setup(self): config = self.container.config[constants.CONFIG_KEY] except KeyError: raise ConfigurationError( - '`{}` config key not found'.format(constants.CONFIG_KEY)) + "`{}` config key not found".format(constants.CONFIG_KEY) + ) try: - username = config['USERNAME'] - password = config['PASSWORD'] - security_token = config['SECURITY_TOKEN'] - sandbox = config['SANDBOX'] + username = config["USERNAME"] + password = config["PASSWORD"] + security_token = config["SECURITY_TOKEN"] + sandbox = config["SANDBOX"] except KeyError as exc: raise ConfigurationError( - '`{}` configuration does not contain mandatory ' - '`{}` key'.format(constants.CONFIG_KEY, exc.args[0]) + "`{}` configuration does not contain mandatory " + "`{}` key".format(constants.CONFIG_KEY, exc.args[0]) ) from exc - api_version = config.get('API_VERSION', constants.DEFAULT_API_VERSION) + api_version = config.get("API_VERSION", constants.DEFAULT_API_VERSION) self.client = get_client( - username, password, security_token, - sandbox=sandbox, api_version=api_version) + username, password, security_token, sandbox=sandbox, api_version=api_version + ) def get_dependency(self, worker_ctx): return self.client diff --git a/nameko_salesforce/api/push_topics.py b/nameko_salesforce/api/push_topics.py index e7a8a42..7ad4d80 100644 --- a/nameko_salesforce/api/push_topics.py +++ b/nameko_salesforce/api/push_topics.py @@ -2,11 +2,11 @@ from cachetools import LRUCache, cachedmethod -from nameko_salesforce.api.client import ClientPool, ClientProxy from nameko_salesforce import constants +from nameko_salesforce.api.client import ClientPool, ClientProxy -cached = cachedmethod(operator.attrgetter('cache')) +cached = cachedmethod(operator.attrgetter("cache")) class NotFound(LookupError): @@ -58,29 +58,24 @@ def declare_push_topic_for_sobject( """ if record_type: - name = '{}{}'.format(sobject_type, record_type) - record_type_id = self.get_record_type_id_by_name( - sobject_type, record_type) + name = "{}{}".format(sobject_type, record_type) + record_type_id = self.get_record_type_id_by_name(sobject_type, record_type) else: name = sobject_type - query = ( - "SELECT Id, Name, LastModifiedById, LastModifiedDate FROM {}" - .format(sobject_type)) + query = "SELECT Id, Name, LastModifiedById, LastModifiedDate FROM {}".format( + sobject_type + ) conditions = [] if record_type: - conditions.append( - "RecordTypeId = '{}'".format(record_type_id)) + conditions.append("RecordTypeId = '{}'".format(record_type_id)) if exclude_current_user: - current_user_id = self.get_user_id_by_name( - self.pool.username) - conditions.append( - "LastModifiedById != '{}'".format(current_user_id)) + current_user_id = self.get_user_id_by_name(self.pool.username) + conditions.append("LastModifiedById != '{}'".format(current_user_id)) if conditions: - query = '{} WHERE {}'.format( - query, ' AND '.join(conditions)) + query = "{} WHERE {}".format(query, " AND ".join(conditions)) return self.declare_push_topic( name=name, @@ -89,7 +84,8 @@ def declare_push_topic_for_sobject( notify_for_operation_create=notify_for_operation_create, notify_for_operation_update=notify_for_operation_update, notify_for_operation_delete=notify_for_operation_delete, - notify_for_operation_undelete=notify_for_operation_undelete) + notify_for_operation_undelete=notify_for_operation_undelete, + ) def declare_push_topic( self, @@ -144,14 +140,14 @@ def declare_push_topic( notify_for_fields = constants.NotifyForFields(notify_for_fields) push_topic_data = { - 'Name': name, - 'Query': query, - 'ApiVersion': self.pool.api_version, - 'NotifyForOperationCreate': notify_for_operation_create, - 'NotifyForOperationUpdate': notify_for_operation_update, - 'NotifyForOperationDelete': notify_for_operation_delete, - 'NotifyForOperationUndelete': notify_for_operation_undelete, - 'NotifyForFields': notify_for_fields.value, + "Name": name, + "Query": query, + "ApiVersion": self.pool.api_version, + "NotifyForOperationCreate": notify_for_operation_create, + "NotifyForOperationUpdate": notify_for_operation_update, + "NotifyForOperationDelete": notify_for_operation_delete, + "NotifyForOperationUndelete": notify_for_operation_undelete, + "NotifyForFields": notify_for_fields.value, } try: @@ -159,36 +155,38 @@ def declare_push_topic( except NotFound: self.PushTopic.create(push_topic_data) else: - self.PushTopic.update(record['Id'], push_topic_data) + self.PushTopic.update(record["Id"], push_topic_data) @cached def get_push_topic_by_name(self, name): - query = ( - "SELECT Id, Name, Query " - "FROM PushTopic WHERE Name = '{}'".format(name)) + query = "SELECT Id, Name, Query " "FROM PushTopic WHERE Name = '{}'".format( + name + ) response = self.query(query) - if response['totalSize'] < 1: + if response["totalSize"] < 1: raise NotFound("PushTopic '{}' does not exist".format(name)) - return response['records'][0] + return response["records"][0] @cached def get_user_id_by_name(self, username): - query = ( - "SELECT Id FROM User WHERE Username = '{}'".format(username)) + query = "SELECT Id FROM User WHERE Username = '{}'".format(username) response = self.query(query) - if response['totalSize'] < 1: + if response["totalSize"] < 1: raise NotFound("User '{}' does not exist".format(username)) - return response['records'][0]['Id'] + return response["records"][0]["Id"] @cached def get_record_type_id_by_name(self, sobject_type, record_type): query = ( "SELECT Id, DeveloperName, SobjectType " "FROM RecordType WHERE SobjectType = '{}' " - "AND DeveloperName = '{}'".format(sobject_type, record_type)) + "AND DeveloperName = '{}'".format(sobject_type, record_type) + ) response = self.query(query) - if response['totalSize'] < 1: + if response["totalSize"] < 1: raise NotFound( - "RecordType '{}' of '{}' does not exist" - .format(sobject_type, record_type)) - return response['records'][0]['Id'] + "RecordType '{}' of '{}' does not exist".format( + sobject_type, record_type + ) + ) + return response["records"][0]["Id"] diff --git a/nameko_salesforce/constants.py b/nameko_salesforce/constants.py index 9d8a489..34aa880 100644 --- a/nameko_salesforce/constants.py +++ b/nameko_salesforce/constants.py @@ -1,37 +1,37 @@ from enum import Enum -CONFIG_KEY = 'SALESFORCE' +CONFIG_KEY = "SALESFORCE" -DEFAULT_API_VERSION = '37.0' +DEFAULT_API_VERSION = "37.0" DEFAULT_REPLAY_STORAGE_TTL = 60 * 60 * 12 -CLIENT_ID_CONTEXT_KEY = 'client_id' +CLIENT_ID_CONTEXT_KEY = "client_id" -REPLAY_ID_CONTEXT_KEY = 'replay_id' +REPLAY_ID_CONTEXT_KEY = "replay_id" class NotifyForFields(Enum): """ Specifies how the records are evaluated against the PushTopic query """ - all_ = 'All' + all_ = "All" """ All record field changes """ - referenced = 'Referenced' + referenced = "Referenced" """ Changes to fields referenced in the SELECT and WHERE query clauses """ - select = 'Select' + select = "Select" """ Changes to fields referenced in the SELECT clause of the query """ - where = 'Where' + where = "Where" """ Changes to fields referenced in the WHERE clause of the query """ diff --git a/nameko_salesforce/streaming/channels.py b/nameko_salesforce/streaming/channels.py index a474a8f..b6b9f09 100644 --- a/nameko_salesforce/streaming/channels.py +++ b/nameko_salesforce/streaming/channels.py @@ -8,7 +8,7 @@ class Subscribe(BaseSubscribe): def compose(self, channel_name, replay_id=None): compose = super(BaseSubscribe, self).compose if replay_id: - ext = {'replay': {channel_name: replay_id}} + ext = {"replay": {channel_name: replay_id}} return compose(subscription=channel_name, ext=ext) else: return compose(subscription=channel_name) diff --git a/nameko_salesforce/streaming/client.py b/nameko_salesforce/streaming/client.py index 181c009..63bb123 100644 --- a/nameko_salesforce/streaming/client.py +++ b/nameko_salesforce/streaming/client.py @@ -1,9 +1,9 @@ -from functools import partial import logging +from functools import partial +import redis from nameko.exceptions import ConfigurationError from nameko_bayeux_client.client import BayeuxClient, BayeuxMessageHandler -import redis from simple_salesforce.login import SalesforceLogin from nameko_salesforce import constants @@ -15,16 +15,15 @@ class StreamingClient(BayeuxClient): - def __init__(self): super().__init__() - self.version = '1.0' + self.version = "1.0" """ Bayeux protocol version """ - self.minimum_version = '1.0' + self.minimum_version = "1.0" """ Minimum Bayeux protocol version @@ -94,44 +93,48 @@ def setup(self): config = self.container.config[constants.CONFIG_KEY] except KeyError: raise ConfigurationError( - '`{}` config key not found'.format(constants.CONFIG_KEY)) + "`{}` config key not found".format(constants.CONFIG_KEY) + ) - self.version = config.get('BAYEUX_VERSION', self.version) + self.version = config.get("BAYEUX_VERSION", self.version) self.minimum_version = config.get( - 'BAYEUX_MINIMUM_VERSION', self.minimum_version) - self.api_version = config.get('API_VERSION', self.api_version) + "BAYEUX_MINIMUM_VERSION", self.minimum_version + ) + self.api_version = config.get("API_VERSION", self.api_version) try: - self.username = config['USERNAME'] - self.password = config['PASSWORD'] - self.security_token = config['SECURITY_TOKEN'] - self.sandbox = config['SANDBOX'] + self.username = config["USERNAME"] + self.password = config["PASSWORD"] + self.security_token = config["SECURITY_TOKEN"] + self.sandbox = config["SANDBOX"] except KeyError as exc: raise ConfigurationError( - '`{}` configuration does not contain mandatory ' - '`{}` key'.format(constants.CONFIG_KEY, exc.args[0]) + "`{}` configuration does not contain mandatory " + "`{}` key".format(constants.CONFIG_KEY, exc.args[0]) ) from exc - self.replay_enabled = config.get('PUSHTOPIC_REPLAY_ENABLED', False) + self.replay_enabled = config.get("PUSHTOPIC_REPLAY_ENABLED", False) if self.replay_enabled: self._setup_replay_storage(config) def _setup_replay_storage(self, config): try: - redis_uri = config['PUSHTOPIC_REPLAY_REDIS_URI'] + redis_uri = config["PUSHTOPIC_REPLAY_REDIS_URI"] except KeyError: raise ConfigurationError( - '`{}` must have `PUSHTOPIC_REPLAY_REDIS_URI` defined if ' - '`PUSHTOPIC_REPLAY_ENABLED` is set to `True`' - .format(constants.CONFIG_KEY) + "`{}` must have `PUSHTOPIC_REPLAY_REDIS_URI` defined if " + "`PUSHTOPIC_REPLAY_ENABLED` is set to `True`".format( + constants.CONFIG_KEY + ) ) self.replay_storage = redis.StrictRedis.from_url(redis_uri) self.replay_storage_ttl = config.get( - 'PUSHTOPIC_REPLAY_TTL', self.replay_storage_ttl) + "PUSHTOPIC_REPLAY_TTL", self.replay_storage_ttl + ) def login(self): - config = self.container.config['SALESFORCE'] + config = self.container.config["SALESFORCE"] access_token, host = SalesforceLogin( session=None, @@ -144,9 +147,9 @@ def login(self): self.access_token = access_token - self.server_uri = 'https://{}/cometd/{}'.format(host, self.api_version) + self.server_uri = "https://{}/cometd/{}".format(host, self.api_version) - logger.info('Logged in to salesforce as %s', config['USERNAME']) + logger.info("Logged in to salesforce as %s", config["USERNAME"]) def subscribe(self): channel = channels.Subscribe(self) @@ -160,14 +163,13 @@ def subscribe(self): self.send_and_handle(subscriptions) def get_authorisation(self): - return 'Bearer', self.access_token + return "Bearer", self.access_token def _format_replay_key(self, channel_name): - return 'salesforce:replay_id:{}'.format(channel_name) + return "salesforce:replay_id:{}".format(channel_name) def get_replay_id(self, channel_name): - replay_id = self.replay_storage.get( - self._format_replay_key(channel_name)) + replay_id = self.replay_storage.get(self._format_replay_key(channel_name)) if replay_id: return int(replay_id) @@ -187,7 +189,7 @@ def handle_message(self, message): args, kwargs = self.get_worker_args(message) - replay_id = message['event']['replayId'] + replay_id = message["event"]["replayId"] context_data = { constants.CLIENT_ID_CONTEXT_KEY: self.client.client_id, @@ -195,8 +197,12 @@ def handle_message(self, message): } self.container.spawn_worker( - self, args, kwargs, context_data=context_data, - handle_result=partial(self.handle_result, replay_id)) + self, + args, + kwargs, + context_data=context_data, + handle_result=partial(self.handle_result, replay_id), + ) def handle_result(self, replay_id, worker_ctx, result, exc_info): if not exc_info and self.client.replay_enabled: @@ -213,12 +219,18 @@ def get_worker_args(self, message): class NotificationsClient(StreamingClient): + def __init__(self): + super().__init__() + self.api_client = None def setup(self): super().setup() self.api_client = push_topics.get_client( - self.username, self.password, self.security_token, - sandbox=self.sandbox, api_version=self.api_version + self.username, + self.password, + self.security_token, + sandbox=self.sandbox, + api_version=self.api_version, ) def start(self): @@ -254,7 +266,7 @@ def __init__( self.declare = True if self.query else False - channel_name = '/topic/{}'.format(name) + channel_name = "/topic/{}".format(name) super().__init__(channel_name) def get_worker_args(self, message): @@ -272,7 +284,8 @@ def declare_push_topic(self, api_client): notify_for_operation_create=self.notify_for_operation_create, notify_for_operation_update=self.notify_for_operation_update, notify_for_operation_delete=self.notify_for_operation_delete, - notify_for_operation_undelete=self.notify_for_operation_undelete) + notify_for_operation_undelete=self.notify_for_operation_undelete, + ) handle_notification = NotificationHandler.decorator @@ -308,10 +321,10 @@ def __init__( self.declare = declare if self.record_type: - topic = '{}{}'.format(sobject_type, record_type) + topic = "{}{}".format(sobject_type, record_type) else: topic = sobject_type - channel_name = '/topic/{}'.format(topic) + channel_name = "/topic/{}".format(topic) super().__init__(channel_name) @@ -331,7 +344,8 @@ def declare_push_topic(self, api_client): notify_for_operation_create=self.notify_for_operation_create, notify_for_operation_update=self.notify_for_operation_update, notify_for_operation_delete=self.notify_for_operation_delete, - notify_for_operation_undelete=self.notify_for_operation_undelete) + notify_for_operation_undelete=self.notify_for_operation_undelete, + ) handle_sobject_notification = SobjectNotificationHandler.decorator diff --git a/setup.py b/setup.py index 9f192fa..d534da4 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,19 @@ #!/usr/bin/env python from setuptools import find_packages, setup + setup( - name='nameko-salesforce', - version='1.2.0', + name="nameko-salesforce", + version="1.2.0", description=( - 'Nameko extension for easy communication with Salesforce ' - '(Including Streaming API)' + "Nameko extension for easy communication with Salesforce " + "(Including Streaming API)" ), - long_description=open('README.rst').read(), - author='Student.com', - author_email='wearehiring@student.com', - url='http://github.com/nameko/nameko-salesforce', - packages=find_packages(exclude=['test', 'test.*']), + long_description=open("README.rst").read(), + author="Student.com", + author_email="ondrej.kohout@gmail.com", + url="http://github.com/nameko/nameko-salesforce", + packages=find_packages(exclude=["test", "test.*"]), install_requires=[ "cachetools", "nameko>=2.8.5", @@ -21,21 +22,12 @@ "simple-salesforce>=0.72.2", ], extras_require={ - 'dev': [ - "coverage", - "flake8", - "pylint", - "pytest", - "requests-mock", - ], - 'docs': [ - 'Sphinx', - 'sphinx-rtd-theme', - ], + "dev": ["coverage", "pre-commit", "pylint", "pytest", "requests-mock"], + "docs": ["Sphinx", "sphinx-rtd-theme"], }, dependency_links=[], zip_safe=True, - license='Apache License, Version 2.0', + license="Apache License, Version 2.0", classifiers=[ "Programming Language :: Python", "Operating System :: MacOS :: MacOS X", @@ -47,5 +39,5 @@ "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", "Intended Audience :: Developers", - ] + ], ) diff --git a/tests/api/test_client.py b/tests/api/test_client.py index 215e4c4..642d6ff 100644 --- a/tests/api/test_client.py +++ b/tests/api/test_client.py @@ -1,22 +1,22 @@ import eventlet -from eventlet.event import Event -from mock import patch import pytest import requests import requests_mock +from eventlet.event import Event +from mock import patch from simple_salesforce import SalesforceResourceNotFound -from nameko_salesforce.api.client import get_client, READ_RETRIES +from nameko_salesforce.api.client import READ_RETRIES, get_client @pytest.fixture def client(config): return get_client( - username=config['SALESFORCE']['USERNAME'], - password=config['SALESFORCE']['PASSWORD'], - security_token=config['SALESFORCE']['SECURITY_TOKEN'], - sandbox=config['SALESFORCE']['SANDBOX'], - api_version=config['SALESFORCE']['SANDBOX'], + username=config["SALESFORCE"]["USERNAME"], + password=config["SALESFORCE"]["PASSWORD"], + security_token=config["SALESFORCE"]["SECURITY_TOKEN"], + sandbox=config["SALESFORCE"]["SANDBOX"], + api_version=config["SALESFORCE"]["SANDBOX"], ) @@ -28,8 +28,8 @@ def mock_salesforce_server(): @pytest.fixture(autouse=True) def mock_salesforce_login(): - with patch('simple_salesforce.api.SalesforceLogin') as SalesforceLogin: - SalesforceLogin.return_value = 'session_id', 'abc.salesforce.com' + with patch("simple_salesforce.api.SalesforceLogin") as SalesforceLogin: + SalesforceLogin.return_value = "session_id", "abc.salesforce.com" yield @@ -37,24 +37,21 @@ def mock_salesforce_login(): def fast_retry(): def no_sleep(period): eventlet.sleep(0) - with patch('nameko.utils.retry.sleep', new=no_sleep): + + with patch("nameko.utils.retry.sleep", new=no_sleep): yield def test_retry_adapter(client): # verify retry adapter is applied to session reads = READ_RETRIES - assert client.session.get_adapter('http://foo').max_retries.read == reads - assert client.session.get_adapter('https://bar').max_retries.read == reads + assert client.session.get_adapter("http://foo").max_retries.read == reads + assert client.session.get_adapter("https://bar").max_retries.read == reads def test_proxy(client, mock_salesforce_server): - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post(requests_mock.ANY, json=response_data) result = client.Contact.create(requests_data) @@ -66,12 +63,8 @@ def test_proxy(client, mock_salesforce_server): def test_concurrency(client, mock_salesforce_server): - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} class Pact: def __init__(self, threshold=2): @@ -108,12 +101,8 @@ def create_contact(): def test_pool_reuses_clients(client, mock_salesforce_server): - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post(requests_mock.ANY, json=response_data) assert client.Contact.create(requests_data) == response_data @@ -124,23 +113,19 @@ def test_pool_reuses_clients(client, mock_salesforce_server): assert len(client.pool.free) == 1 -@pytest.mark.usefixtures('fast_retry') +@pytest.mark.usefixtures("fast_retry") def test_bad_clients_are_discarded(client, mock_salesforce_server): # first call is successful; second is session expired; third is successful - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post( requests_mock.ANY, [ - {'json': response_data}, - {'status_code': 401, 'text': 'session expired'}, - {'json': response_data}, - ] + {"json": response_data}, + {"status_code": 401, "text": "session expired"}, + {"json": response_data}, + ], ) assert client.Contact.create(requests_data) == response_data @@ -157,22 +142,15 @@ def test_bad_clients_are_discarded(client, mock_salesforce_server): assert len(client.pool.free) == 1 -@pytest.mark.usefixtures('fast_retry') +@pytest.mark.usefixtures("fast_retry") def test_bad_connections_are_discarded(client, mock_salesforce_server): # first call is successful; second is ConnectionError - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post( requests_mock.ANY, - [ - {'json': response_data}, - {'exc': requests.exceptions.ConnectionError} - ] + [{"json": response_data}, {"exc": requests.exceptions.ConnectionError}], ) assert client.Contact.create(requests_data) == response_data @@ -190,22 +168,15 @@ def test_bad_connections_are_discarded(client, mock_salesforce_server): assert len(client.pool.free) == 0 -@pytest.mark.usefixtures('fast_retry') +@pytest.mark.usefixtures("fast_retry") def test_proxy_retries_on_session_expired(client, mock_salesforce_server): # first call is session expired; second is success - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post( requests_mock.ANY, - [ - {'status_code': 401, 'text': 'session expired'}, - {'json': response_data}, - ] + [{"status_code": 401, "text": "session expired"}, {"json": response_data}], ) # retry succeeds @@ -216,17 +187,17 @@ def test_proxy_retries_on_session_expired(client, mock_salesforce_server): assert len(client.pool.free) == 1 -@pytest.mark.usefixtures('fast_retry') +@pytest.mark.usefixtures("fast_retry") def test_other_salesforce_errors_are_raised(client, mock_salesforce_server): # first call is session expired; second is a 404 - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} + requests_data = {"LastName": "Smith", "Email": "example@example.com"} mock_salesforce_server.post( requests_mock.ANY, [ - {'status_code': 401, 'text': 'session expired'}, - {'status_code': 404, 'text': 'not found'}, - ] + {"status_code": 401, "text": "session expired"}, + {"status_code": 404, "text": "not found"}, + ], ) with pytest.raises(SalesforceResourceNotFound): diff --git a/tests/api/test_dependency.py b/tests/api/test_dependency.py index 8c0d4af..f734bd7 100644 --- a/tests/api/test_dependency.py +++ b/tests/api/test_dependency.py @@ -1,25 +1,22 @@ +import pytest +import requests_mock from mock import Mock, patch from nameko.containers import ServiceContainer from nameko.exceptions import ConfigurationError from nameko.testing.services import dummy, entrypoint_hook -import pytest -import requests_mock from nameko_salesforce import constants from nameko_salesforce.api import SalesforceAPI class TestSalesforceAPIUnit: - @pytest.fixture def container(self, config): - return Mock( - spec=ServiceContainer, config=config, service_name='exampleservice' - ) + return Mock(spec=ServiceContainer, config=config, service_name="exampleservice") @pytest.fixture def dependency_provider(self, container): - return SalesforceAPI().bind(container, 'salesforce_api') + return SalesforceAPI().bind(container, "salesforce_api") @pytest.fixture def salesforce_api(self, dependency_provider): @@ -30,69 +27,53 @@ def test_setup(self, config, salesforce_api): salesforce_config = config[constants.CONFIG_KEY] + assert salesforce_api.client.pool.username == salesforce_config["USERNAME"] + assert salesforce_api.client.pool.password == salesforce_config["PASSWORD"] assert ( - salesforce_api.client.pool.username == - salesforce_config['USERNAME']) - assert ( - salesforce_api.client.pool.password == - salesforce_config['PASSWORD']) - assert ( - salesforce_api.client.pool.security_token == - salesforce_config['SECURITY_TOKEN']) - assert ( - salesforce_api.client.pool.sandbox == - salesforce_config['SANDBOX']) + salesforce_api.client.pool.security_token + == salesforce_config["SECURITY_TOKEN"] + ) + assert salesforce_api.client.pool.sandbox == salesforce_config["SANDBOX"] assert ( - salesforce_api.client.pool.api_version == - salesforce_config['API_VERSION']) + salesforce_api.client.pool.api_version == salesforce_config["API_VERSION"] + ) - def test_setup_default_api_version( - self, config, salesforce_api - ): - config[constants.CONFIG_KEY].pop('API_VERSION') + def test_setup_default_api_version(self, config, salesforce_api): + config[constants.CONFIG_KEY].pop("API_VERSION") - assert ( - salesforce_api.client.pool.api_version == - constants.DEFAULT_API_VERSION) + assert salesforce_api.client.pool.api_version == constants.DEFAULT_API_VERSION - def test_setup_main_config_key_missing( - self, config, dependency_provider - ): + def test_setup_main_config_key_missing(self, config, dependency_provider): - config.pop('SALESFORCE') + config.pop("SALESFORCE") with pytest.raises(ConfigurationError) as exc: dependency_provider.setup() - assert str(exc.value) == '`SALESFORCE` config key not found' + assert str(exc.value) == "`SALESFORCE` config key not found" @pytest.mark.parametrize( - 'key', ('USERNAME', 'PASSWORD', 'SECURITY_TOKEN', 'SANDBOX') + "key", ("USERNAME", "PASSWORD", "SECURITY_TOKEN", "SANDBOX") ) - def test_setup_config_keys_missing( - self, config, dependency_provider, key - ): + def test_setup_config_keys_missing(self, config, dependency_provider, key): config[constants.CONFIG_KEY].pop(key) with pytest.raises(ConfigurationError) as exc: dependency_provider.setup() - expected_error = ( - '`{}` configuration does not contain mandatory `{}` key' - .format(constants.CONFIG_KEY, key)) - assert str(exc.value) == expected_error + expected_error = "`{}` configuration does not contain mandatory `{}` key" + assert str(exc.value) == expected_error.format(constants.CONFIG_KEY, key) def test_get_dependency(self, config, dependency_provider): dependency_provider.setup() worker_ctx = Mock() assert ( - dependency_provider.get_dependency(worker_ctx) == - dependency_provider.client) + dependency_provider.get_dependency(worker_ctx) == dependency_provider.client + ) class TestSalesforceAPIEndToEnd: - @pytest.fixture def mock_salesforce_server(self): with requests_mock.Mocker() as mocked_requests: @@ -100,20 +81,14 @@ def mock_salesforce_server(self): @pytest.fixture(autouse=True) def mock_salesforce_login(self): - with patch('simple_salesforce.api.SalesforceLogin') as SalesforceLogin: - SalesforceLogin.return_value = 'session_id', 'abc.salesforce.com' + with patch("simple_salesforce.api.SalesforceLogin") as SalesforceLogin: + SalesforceLogin.return_value = "session_id", "abc.salesforce.com" yield - def test_end_to_end( - self, config, container_factory, mock_salesforce_server - ): + def test_end_to_end(self, config, container_factory, mock_salesforce_server): - requests_data = {'LastName': 'Smith', 'Email': 'example@example.com'} - response_data = { - 'errors': [], - 'id': '003e0000003GuNXAA0', - 'success': True - } + requests_data = {"LastName": "Smith", "Email": "example@example.com"} + response_data = {"errors": [], "id": "003e0000003GuNXAA0", "success": True} mock_salesforce_server.post(requests_mock.ANY, json=response_data) class Service(object): @@ -129,9 +104,7 @@ def create(self, requests_data): container = container_factory(Service, config) container.start() - with entrypoint_hook(container, 'create') as create: + with entrypoint_hook(container, "create") as create: assert create(requests_data) == response_data - assert ( - mock_salesforce_server.request_history[0].json() == - requests_data) + assert mock_salesforce_server.request_history[0].json() == requests_data diff --git a/tests/api/test_push_topics.py b/tests/api/test_push_topics.py index 8b81371..3c93885 100644 --- a/tests/api/test_push_topics.py +++ b/tests/api/test_push_topics.py @@ -1,26 +1,26 @@ -from mock import patch import pytest import requests_mock +from mock import patch from nameko_salesforce import constants from nameko_salesforce.api.client import ClientProxy -from nameko_salesforce.api.push_topics import get_client, PushTopicsAPIClient +from nameko_salesforce.api.push_topics import PushTopicsAPIClient, get_client @pytest.fixture def client(config): return get_client( - username=config['SALESFORCE']['USERNAME'], - password=config['SALESFORCE']['PASSWORD'], - security_token=config['SALESFORCE']['SECURITY_TOKEN'], - sandbox=config['SALESFORCE']['SANDBOX'], - api_version=config['SALESFORCE']['SANDBOX'], + username=config["SALESFORCE"]["USERNAME"], + password=config["SALESFORCE"]["PASSWORD"], + security_token=config["SALESFORCE"]["SECURITY_TOKEN"], + sandbox=config["SALESFORCE"]["SANDBOX"], + api_version=config["SALESFORCE"]["SANDBOX"], ) @pytest.fixture def api_version(config): - return config['SALESFORCE']['API_VERSION'] + return config["SALESFORCE"]["API_VERSION"] @pytest.fixture @@ -31,8 +31,8 @@ def mock_salesforce_server(): @pytest.fixture(autouse=True) def mock_salesforce_login(): - with patch('simple_salesforce.api.SalesforceLogin') as SalesforceLogin: - SalesforceLogin.return_value = 'session_id', 'abc.salesforce.com' + with patch("simple_salesforce.api.SalesforceLogin") as SalesforceLogin: + SalesforceLogin.return_value = "session_id", "abc.salesforce.com" yield @@ -40,208 +40,176 @@ def test_implements_client_proxy(client): assert isinstance(client, ClientProxy) -def test_get_push_topic_by_name_not_found( - api_version, client, mock_salesforce_server -): - response_data = {'totalSize': 0, 'records': []} +def test_get_push_topic_by_name_not_found(api_version, client, mock_salesforce_server): + response_data = {"totalSize": 0, "records": []} mock_salesforce_server.get(requests_mock.ANY, json=response_data) with pytest.raises(client.NotFound) as exc: - client.get_push_topic_by_name('ContactUpdates') + client.get_push_topic_by_name("ContactUpdates") assert str(exc.value) == "PushTopic 'ContactUpdates' does not exist" def test_get_push_topic_by_name(api_version, client, mock_salesforce_server): - name = 'ContactUpdates' + name = "ContactUpdates" response_data = { - 'totalSize': 1, - 'records': [{'Id': '00..A0', 'Name': name, 'Query': '...'}] + "totalSize": 1, + "records": [{"Id": "00..A0", "Name": name, "Query": "..."}], } mock_salesforce_server.get(requests_mock.ANY, json=response_data) result = client.get_push_topic_by_name(name) - assert result == {'Id': '00..A0', 'Name': name, 'Query': '...'} + assert result == {"Id": "00..A0", "Name": name, "Query": "..."} assert len(mock_salesforce_server.request_history) == 1 request = mock_salesforce_server.request_history[0] - assert request.path == '/services/data/v{}/query/'.format(api_version) + assert request.path == "/services/data/v{}/query/".format(api_version) assert request.query == ( - 'q=select+id%2c+name%2c+query+from+pushtopic+where+name+%3d+%27' - 'contactupdates%27' + "q=select+id%2c+name%2c+query+from+pushtopic+where+name+%3d+%27" + "contactupdates%27" ) -def test_get_push_topic_by_name_cached( - api_version, client, mock_salesforce_server -): - name = 'ContactUpdates' +def test_get_push_topic_by_name_cached(api_version, client, mock_salesforce_server): + name = "ContactUpdates" response_data = { - 'totalSize': 1, - 'records': [{'Id': '00..A0', 'Name': name, 'Query': '...'}] + "totalSize": 1, + "records": [{"Id": "00..A0", "Name": name, "Query": "..."}], } mock_salesforce_server.get(requests_mock.ANY, json=response_data) result = client.get_push_topic_by_name(name) - assert result == {'Id': '00..A0', 'Name': name, 'Query': '...'} + assert result == {"Id": "00..A0", "Name": name, "Query": "..."} assert len(mock_salesforce_server.request_history) == 1 result = client.get_push_topic_by_name(name) - assert result == {'Id': '00..A0', 'Name': name, 'Query': '...'} + assert result == {"Id": "00..A0", "Name": name, "Query": "..."} assert len(mock_salesforce_server.request_history) == 1 # requested once -def test_get_user_id_by_name_not_found( - api_version, client, mock_salesforce_server -): - response_data = {'totalSize': 0, 'records': []} +def test_get_user_id_by_name_not_found(api_version, client, mock_salesforce_server): + response_data = {"totalSize": 0, "records": []} mock_salesforce_server.get(requests_mock.ANY, json=response_data) with pytest.raises(client.NotFound) as exc: - client.get_user_id_by_name('smith') + client.get_user_id_by_name("smith") assert str(exc.value) == "User 'smith' does not exist" def test_get_user_id_by_name(api_version, client, mock_salesforce_server): - name = 'smith' - response_data = { - 'totalSize': 1, - 'records': [{'Id': '00..A0', 'Username': name}] - } + name = "smith" + response_data = {"totalSize": 1, "records": [{"Id": "00..A0", "Username": name}]} mock_salesforce_server.get(requests_mock.ANY, json=response_data) result = client.get_user_id_by_name(name) - assert result == '00..A0' + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 request = mock_salesforce_server.request_history[0] - assert request.path == '/services/data/v{}/query/'.format(api_version) - assert request.query == ( - 'q=select+id+from+user+where+username+%3d+%27smith%27') + assert request.path == "/services/data/v{}/query/".format(api_version) + assert request.query == ("q=select+id+from+user+where+username+%3d+%27smith%27") -def test_get_user_id_by_name_cached( - api_version, client, mock_salesforce_server -): - name = 'ContactUpdates' - response_data = { - 'totalSize': 1, - 'records': [{'Id': '00..A0', 'Username': name}] - } +def test_get_user_id_by_name_cached(api_version, client, mock_salesforce_server): + name = "ContactUpdates" + response_data = {"totalSize": 1, "records": [{"Id": "00..A0", "Username": name}]} mock_salesforce_server.get(requests_mock.ANY, json=response_data) result = client.get_user_id_by_name(name) - assert result == '00..A0' + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 result = client.get_user_id_by_name(name) - assert result == '00..A0' + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 # requested once def test_get_record_type_id_by_name_not_found( api_version, client, mock_salesforce_server ): - response_data = {'totalSize': 0, 'records': []} + response_data = {"totalSize": 0, "records": []} mock_salesforce_server.get(requests_mock.ANY, json=response_data) with pytest.raises(client.NotFound) as exc: - client.get_record_type_id_by_name('Student', 'Contact') + client.get_record_type_id_by_name("Student", "Contact") - assert ( - str(exc.value) == "RecordType 'Student' of 'Contact' does not exist") + assert str(exc.value) == "RecordType 'Student' of 'Contact' does not exist" -def test_get_record_type_id_by_name( - api_version, client, mock_salesforce_server -): +def test_get_record_type_id_by_name(api_version, client, mock_salesforce_server): - sobject_type_name = 'Contact' - record_typ_name = 'Student' - response_data = {'totalSize': 1, 'records': [{'Id': '00..A0'}]} + sobject_type_name = "Contact" + record_typ_name = "Student" + response_data = {"totalSize": 1, "records": [{"Id": "00..A0"}]} mock_salesforce_server.get(requests_mock.ANY, json=response_data) - result = client.get_record_type_id_by_name( - sobject_type_name, record_typ_name) + result = client.get_record_type_id_by_name(sobject_type_name, record_typ_name) - assert result == '00..A0' + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 request = mock_salesforce_server.request_history[0] - assert request.path == '/services/data/v{}/query/'.format(api_version) + assert request.path == "/services/data/v{}/query/".format(api_version) assert request.query == ( - 'q=select+id%2c+developername%2c+sobjecttype+from+recordtype+where+' - 'sobjecttype+%3d+%27contact%27+and+developername+%3d+%27student%27' + "q=select+id%2c+developername%2c+sobjecttype+from+recordtype+where+" + "sobjecttype+%3d+%27contact%27+and+developername+%3d+%27student%27" ) -def test_get_record_type_id_by_name_cached( - api_version, client, mock_salesforce_server -): - sobject_type_name = 'Contact' - record_typ_name = 'Student' - response_data = {'totalSize': 1, 'records': [{'Id': '00..A0'}]} +def test_get_record_type_id_by_name_cached(api_version, client, mock_salesforce_server): + sobject_type_name = "Contact" + record_typ_name = "Student" + response_data = {"totalSize": 1, "records": [{"Id": "00..A0"}]} mock_salesforce_server.get(requests_mock.ANY, json=response_data) - result = client.get_record_type_id_by_name( - sobject_type_name, record_typ_name) - assert result == '00..A0' + result = client.get_record_type_id_by_name(sobject_type_name, record_typ_name) + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 - result = client.get_record_type_id_by_name( - sobject_type_name, record_typ_name) - assert result == '00..A0' + result = client.get_record_type_id_by_name(sobject_type_name, record_typ_name) + assert result == "00..A0" assert len(mock_salesforce_server.request_history) == 1 # requested once class TestDeclarePushTopic: - @pytest.fixture def get_record_type_id_by_name(self): - with patch.object( - PushTopicsAPIClient, 'get_record_type_id_by_name' - ) as method: + with patch.object(PushTopicsAPIClient, "get_record_type_id_by_name") as method: yield method @pytest.fixture def get_user_id_by_name(self): - with patch.object( - PushTopicsAPIClient, 'get_user_id_by_name' - ) as method: + with patch.object(PushTopicsAPIClient, "get_user_id_by_name") as method: yield method @pytest.fixture def get_push_topic_by_name(self): - with patch.object( - PushTopicsAPIClient, 'get_push_topic_by_name' - ) as method: + with patch.object(PushTopicsAPIClient, "get_push_topic_by_name") as method: yield method - @pytest.fixture(params=('create', 'update')) + @pytest.fixture(params=("create", "update")) def write_push_topic( self, request, client, get_push_topic_by_name, mock_salesforce_server ): - if request.param == 'create': + if request.param == "create": get_push_topic_by_name.side_effect = client.NotFound - create_push_topic = mock_salesforce_server.post( - requests_mock.ANY, json={}) + create_push_topic = mock_salesforce_server.post(requests_mock.ANY, json={}) return create_push_topic - if request.param == 'update': - get_push_topic_by_name.return_value = {'Id': '00..A0'} - update_push_topic = mock_salesforce_server.patch( - requests_mock.ANY, json={}) + if request.param == "update": + get_push_topic_by_name.return_value = {"Id": "00..A0"} + update_push_topic = mock_salesforce_server.patch(requests_mock.ANY, json={}) return update_push_topic - @pytest.fixture(params=('declare', 'declare_for_sobject')) + @pytest.fixture(params=("declare", "declare_for_sobject")) def call_declare(self, request, client): map_ = { - 'declare': client.declare_push_topic, - 'declare_for_sobject': client.declare_push_topic_for_sobject, + "declare": client.declare_push_topic, + "declare_for_sobject": client.declare_push_topic_for_sobject, } return map_[request.param] @@ -254,10 +222,10 @@ def test_push_topic_created( and ``declare_push_topic_for_sobject``. """ - sobject_type = 'Contact' + sobject_type = "Contact" # setup push topic lookup - response_data = {'totalSize': 0, 'records': []} + response_data = {"totalSize": 0, "records": []} mock_salesforce_server.get(requests_mock.ANY, json=response_data) # setup push topic creation @@ -267,13 +235,13 @@ def test_push_topic_created( get_request, post_request = mock_salesforce_server.request_history - assert get_request.method == 'GET' - assert get_request.path == ( - '/services/data/v{}/query/'.format(api_version)) + assert get_request.method == "GET" + assert get_request.path == ("/services/data/v{}/query/".format(api_version)) - assert post_request.method == 'POST' + assert post_request.method == "POST" assert post_request.path == ( - '/services/data/v{}/sobjects/pushtopic/'.format(api_version)) + "/services/data/v{}/sobjects/pushtopic/".format(api_version) + ) def test_push_topic_updated( self, api_version, call_declare, mock_salesforce_server @@ -284,10 +252,10 @@ def test_push_topic_updated( and ``declare_push_topic_for_sobject``. """ - sobject_type = 'Contact' + sobject_type = "Contact" # push topic lookup response - response_data = {'totalSize': 1, 'records': [{'Id': '00..A0'}]} + response_data = {"totalSize": 1, "records": [{"Id": "00..A0"}]} mock_salesforce_server.get(requests_mock.ANY, json=response_data) # push topic update response @@ -297,17 +265,16 @@ def test_push_topic_updated( get_request, post_request = mock_salesforce_server.request_history - assert get_request.method == 'GET' - assert get_request.path == ( - '/services/data/v{}/query/'.format(api_version)) + assert get_request.method == "GET" + assert get_request.path == ("/services/data/v{}/query/".format(api_version)) - assert post_request.method == 'PATCH' + assert post_request.method == "PATCH" assert post_request.path == ( - '/services/data/v{}/sobjects/pushtopic/00..a0' - .format(api_version)) + "/services/data/v{}/sobjects/pushtopic/00..a0".format(api_version) + ) def test_push_topic_name( - self, get_record_type_id_by_name, client, write_push_topic, + self, get_record_type_id_by_name, client, write_push_topic ): """ Test push topic name definition @@ -315,23 +282,25 @@ def test_push_topic_name( push topic object and for an update of existing one. """ - name = 'ContactUpdated' + name = "ContactUpdated" client.declare_push_topic(name) request = write_push_topic.request_history[0] - assert request.json()['Name'] == name + assert request.json()["Name"] == name @pytest.mark.parametrize( - ('sobject_type', 'record_type', 'expected_name'), - ( - ('Contact', None, 'Contact'), - ('Contact', 'Student', 'ContactStudent'), - ) + ("sobject_type", "record_type", "expected_name"), + (("Contact", None, "Contact"), ("Contact", "Student", "ContactStudent")), ) def test_push_topic_name_for_sobject( - self, get_record_type_id_by_name, client, write_push_topic, - sobject_type, record_type, expected_name + self, + get_record_type_id_by_name, + client, + write_push_topic, + sobject_type, + record_type, + expected_name, ): """ Test push topic name definition for sObject @@ -342,11 +311,10 @@ def test_push_topic_name_for_sobject( client.declare_push_topic_for_sobject(sobject_type, record_type) request = write_push_topic.request_history[0] - assert request.json()['Name'] == expected_name + assert request.json()["Name"] == expected_name def test_push_topic_query( - self, get_record_type_id_by_name, get_user_id_by_name, - client, write_push_topic, + self, get_record_type_id_by_name, get_user_id_by_name, client, write_push_topic ): """ Test push topic query definition @@ -354,48 +322,62 @@ def test_push_topic_query( push topic object and for an update of existing one. """ - name = 'ContactUpdated' - query = 'SELECT ...' + name = "ContactUpdated" + query = "SELECT ..." - get_record_type_id_by_name.return_value = '00..A0' - get_user_id_by_name.return_value = '11..A1' + get_record_type_id_by_name.return_value = "00..A0" + get_user_id_by_name.return_value = "11..A1" client.declare_push_topic(name, query) request = write_push_topic.request_history[0] - assert request.json()['Name'] == name - assert request.json()['Query'] == query + assert request.json()["Name"] == name + assert request.json()["Query"] == query @pytest.mark.parametrize( - ('record_type', 'exclude_current_user', 'expected_query'), + ("record_type", "exclude_current_user", "expected_query"), ( ( - None, False, - ("SELECT Id, Name, LastModifiedById, LastModifiedDate " - "FROM Contact"), + None, + False, + ("SELECT Id, Name, LastModifiedById, LastModifiedDate " "FROM Contact"), ), ( - 'Student', False, - ("SELECT Id, Name, LastModifiedById, LastModifiedDate " - "FROM Contact WHERE RecordTypeId = '00..A0'"), + "Student", + False, + ( + "SELECT Id, Name, LastModifiedById, LastModifiedDate " + "FROM Contact WHERE RecordTypeId = '00..A0'" + ), ), ( - None, True, - ("SELECT Id, Name, LastModifiedById, LastModifiedDate " - "FROM Contact WHERE LastModifiedById != '11..A1'"), + None, + True, + ( + "SELECT Id, Name, LastModifiedById, LastModifiedDate " + "FROM Contact WHERE LastModifiedById != '11..A1'" + ), ), ( - 'Student', True, - ("SELECT Id, Name, LastModifiedById, LastModifiedDate " - "FROM Contact WHERE RecordTypeId = '00..A0' " - "AND LastModifiedById != '11..A1'"), + "Student", + True, + ( + "SELECT Id, Name, LastModifiedById, LastModifiedDate " + "FROM Contact WHERE RecordTypeId = '00..A0' " + "AND LastModifiedById != '11..A1'" + ), ), - ) + ), ) def test_push_topic_query_for_sobject( - self, get_record_type_id_by_name, get_user_id_by_name, - client, write_push_topic, - record_type, exclude_current_user, expected_query + self, + get_record_type_id_by_name, + get_user_id_by_name, + client, + write_push_topic, + record_type, + exclude_current_user, + expected_query, ): """ Test push topic query definition for sObject @@ -403,32 +385,35 @@ def test_push_topic_query_for_sobject( push topic object and for an update of existing one. """ - sobject_type = 'Contact' + sobject_type = "Contact" - get_record_type_id_by_name.return_value = '00..A0' - get_user_id_by_name.return_value = '11..A1' + get_record_type_id_by_name.return_value = "00..A0" + get_user_id_by_name.return_value = "11..A1" client.declare_push_topic_for_sobject( - sobject_type, record_type, - exclude_current_user=exclude_current_user) + sobject_type, record_type, exclude_current_user=exclude_current_user + ) request = write_push_topic.request_history[0] - assert request.json()['Query'] == expected_query + assert request.json()["Query"] == expected_query @pytest.mark.parametrize( - ('notify_for_fields_input', 'expected_notify_for_fields'), + ("notify_for_fields_input", "expected_notify_for_fields"), ( (None, constants.NotifyForFields.all_.value), - ('Referenced', constants.NotifyForFields.referenced.value), + ("Referenced", constants.NotifyForFields.referenced.value), ( constants.NotifyForFields.referenced, - constants.NotifyForFields.referenced.value + constants.NotifyForFields.referenced.value, ), - ) + ), ) def test_notify_for_fields( - self, call_declare, write_push_topic, - notify_for_fields_input, expected_notify_for_fields + self, + call_declare, + write_push_topic, + notify_for_fields_input, + expected_notify_for_fields, ): """ Test pushh topic NotifyForFields definition @@ -441,61 +426,29 @@ def test_notify_for_fields( """ if notify_for_fields_input: - call_declare('Contact', notify_for_fields=notify_for_fields_input) + call_declare("Contact", notify_for_fields=notify_for_fields_input) else: - call_declare('Contact') + call_declare("Contact") request = write_push_topic.request_history[0] - assert request.json()['NotifyForFields'] == expected_notify_for_fields + assert request.json()["NotifyForFields"] == expected_notify_for_fields @pytest.mark.parametrize( - ('arg_name', 'option_name', 'value'), + ("arg_name", "option_name", "value"), ( - (None, 'NotifyForOperationCreate', True), - (None, 'NotifyForOperationUpdate', True), - (None, 'NotifyForOperationDelete', True), - (None, 'NotifyForOperationUndelete', True), - ( - 'notify_for_operation_create', - 'NotifyForOperationCreate', - True - ), - ( - 'notify_for_operation_update', - 'NotifyForOperationUpdate', - True - ), - ( - 'notify_for_operation_delete', - 'NotifyForOperationDelete', - True - ), - ( - 'notify_for_operation_undelete', - 'NotifyForOperationUndelete', - True - ), - ( - 'notify_for_operation_create', - 'NotifyForOperationCreate', - False - ), - ( - 'notify_for_operation_update', - 'NotifyForOperationUpdate', - False - ), - ( - 'notify_for_operation_delete', - 'NotifyForOperationDelete', - False - ), - ( - 'notify_for_operation_undelete', - 'NotifyForOperationUndelete', - False - ), - ) + (None, "NotifyForOperationCreate", True), + (None, "NotifyForOperationUpdate", True), + (None, "NotifyForOperationDelete", True), + (None, "NotifyForOperationUndelete", True), + ("notify_for_operation_create", "NotifyForOperationCreate", True), + ("notify_for_operation_update", "NotifyForOperationUpdate", True), + ("notify_for_operation_delete", "NotifyForOperationDelete", True), + ("notify_for_operation_undelete", "NotifyForOperationUndelete", True), + ("notify_for_operation_create", "NotifyForOperationCreate", False), + ("notify_for_operation_update", "NotifyForOperationUpdate", False), + ("notify_for_operation_delete", "NotifyForOperationDelete", False), + ("notify_for_operation_undelete", "NotifyForOperationUndelete", False), + ), ) def test_notify_for_operation( self, call_declare, write_push_topic, arg_name, option_name, value @@ -511,9 +464,9 @@ def test_notify_for_operation( """ if arg_name: - call_declare('Contact', **{arg_name: value}) + call_declare("Contact", **{arg_name: value}) else: - call_declare('Contact') + call_declare("Contact") request = write_push_topic.request_history[0] assert request.json()[option_name] == value diff --git a/tests/conftest.py b/tests/conftest.py index ea77ddf..f16f323 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,11 +4,11 @@ @pytest.fixture def config(): config = {} - config['SALESFORCE'] = { - 'USERNAME': 'Rocky', - 'PASSWORD': 'Balboa', - 'SECURITY_TOKEN': 'ABCD1234', - 'SANDBOX': False, - 'API_VERSION': '37.0', + config["SALESFORCE"] = { + "USERNAME": "Rocky", + "PASSWORD": "Balboa", + "SECURITY_TOKEN": "ABCD1234", + "SANDBOX": False, + "API_VERSION": "37.0", } return config diff --git a/tests/streaming/conftest.py b/tests/streaming/conftest.py index b1e2049..72e78ef 100644 --- a/tests/streaming/conftest.py +++ b/tests/streaming/conftest.py @@ -4,16 +4,15 @@ @pytest.fixture def config(config): - config['SALESFORCE'].update({ - 'BAYEUX_VERSION': '1.0', - 'BAYEUX_MINIMUM_VERSION': '1.0', - }) + config["SALESFORCE"].update( + {"BAYEUX_VERSION": "1.0", "BAYEUX_MINIMUM_VERSION": "1.0"} + ) return config @pytest.fixture def redis_uri(): - return 'redis://localhost:6379/11' + return "redis://localhost:6379/11" @pytest.yield_fixture diff --git a/tests/streaming/test_end_to_end.py b/tests/streaming/test_end_to_end.py index 949fa89..ce2e090 100644 --- a/tests/streaming/test_end_to_end.py +++ b/tests/streaming/test_end_to_end.py @@ -1,18 +1,18 @@ import json +import pytest +import requests_mock from eventlet import sleep from eventlet.event import Event -from mock import call, Mock, patch +from mock import Mock, call, patch from nameko.testing.utils import find_free_port from nameko.web.handlers import http from nameko_bayeux_client.constants import Reconnection -import pytest -import requests_mock from nameko_salesforce import constants from nameko_salesforce.streaming import ( - handle_sobject_notification, handle_notification, + handle_sobject_notification, subscribe, ) from nameko_salesforce.streaming.client import StreamingClient @@ -20,7 +20,7 @@ @pytest.fixture def client_id(): - return '5b1jdngw1jz9g9w176s5z4jha0h8' + return "5b1jdngw1jz9g9w176s5z4jha0h8" @pytest.fixture @@ -29,98 +29,88 @@ def message_maker(config, client_id): # TODO - nameko_bayeux_client should make these fixtures available? class MessageMaker: - def make_handshake_request(self, **fields): message = { - 'channel': '/meta/handshake', - 'id': 1, - 'version': config['SALESFORCE']['BAYEUX_VERSION'], - 'minimumVersion': ( - config['SALESFORCE']['BAYEUX_MINIMUM_VERSION']), - 'supportedConnectionTypes': ['long-polling'], + "channel": "/meta/handshake", + "id": 1, + "version": config["SALESFORCE"]["BAYEUX_VERSION"], + "minimumVersion": (config["SALESFORCE"]["BAYEUX_MINIMUM_VERSION"]), + "supportedConnectionTypes": ["long-polling"], } message.update(**fields) return message def make_subscribe_request(self, **fields): message = { - 'clientId': client_id, - 'channel': '/meta/subscribe', - 'id': 2, - 'subscription': '/topic/example', + "clientId": client_id, + "channel": "/meta/subscribe", + "id": 2, + "subscription": "/topic/example", } message.update(**fields) return message def make_connect_request(self, **fields): message = { - 'clientId': client_id, - 'id': 4, - 'channel': '/meta/connect', - 'connectionType': 'long-polling', + "clientId": client_id, + "id": 4, + "channel": "/meta/connect", + "connectionType": "long-polling", } message.update(**fields) return message def make_disconnect_request(self, **fields): - message = { - 'clientId': client_id, - 'id': 5, - 'channel': '/meta/disconnect', - } + message = {"clientId": client_id, "id": 5, "channel": "/meta/disconnect"} message.update(**fields) return message def make_event_delivery_message(self, **fields): - message = { - 'data': [], - 'channel': '/topic/example', - 'clientId': client_id, - } + message = {"data": [], "channel": "/topic/example", "clientId": client_id} message.update(**fields) return message def make_handshake_response(self, **fields): message = { - 'successful': True, - 'id': '1', - 'channel': '/meta/handshake', - 'version': '1.0', - 'minimumVersion': '1.0', - 'clientId': client_id, - 'supportedConnectionTypes': ['long-polling'], - 'ext': {'replay': True}, + "successful": True, + "id": "1", + "channel": "/meta/handshake", + "version": "1.0", + "minimumVersion": "1.0", + "clientId": client_id, + "supportedConnectionTypes": ["long-polling"], + "ext": {"replay": True}, } message.update(**fields) return message def make_subscribe_response(self, **fields): message = { - 'successful': True, - 'id': '1', - 'channel': '/meta/subscribe', - 'clientId': client_id, - 'subscription': '/spam/ham', + "successful": True, + "id": "1", + "channel": "/meta/subscribe", + "clientId": client_id, + "subscription": "/spam/ham", } message.update(**fields) return message def make_connect_response(self, **fields): message = { - 'successful': True, - 'id': '1', - 'channel': '/meta/connect', - 'clientId': client_id, + "successful": True, + "id": "1", + "channel": "/meta/connect", + "clientId": client_id, } message.update(**fields) return message def make_disconnect_response(self, **fields): message = { - 'successful': True, - 'id': '1', - 'channel': '/meta/disconnect', - 'clientId': client_id, + "successful": True, + "id": "1", + "channel": "/meta/disconnect", + "clientId": client_id, } message.update(**fields) return message @@ -132,39 +122,36 @@ def make_disconnect_response(self, **fields): def notifications(message_maker): return [ { - 'event': { - 'createdDate': '2016-03-29T16:40:08.208Z', - 'replayId': 1, - 'type': 'created', + "event": { + "createdDate": "2016-03-29T16:40:08.208Z", + "replayId": 1, + "type": "created", + }, + "sobject": { + "Id": "001D000000KnaXjIAJ", + "FirstName": "Rocky", + "LastName": "Balboa", }, - 'sobject': { - 'Id': '001D000000KnaXjIAJ', - 'FirstName': 'Rocky', - 'LastName': 'Balboa', - } }, { - 'event': { - 'createdDate': '2016-03-29T16:40:08.208Z', - 'replayId': 2, - 'type': 'updated', + "event": { + "createdDate": "2016-03-29T16:40:08.208Z", + "replayId": 2, + "type": "updated", }, - 'sobject': { - 'Id': '006D000000KnaXjIAJ', - 'Name': 'TicTacToe' - } + "sobject": {"Id": "006D000000KnaXjIAJ", "Name": "TicTacToe"}, }, { - 'event': { - 'createdDate': '2016-03-29T16:40:08.208Z', - 'replayId': 3, - 'type': 'created', + "event": { + "createdDate": "2016-03-29T16:40:08.208Z", + "replayId": 3, + "type": "created", + }, + "sobject": { + "Id": "004D000000KnaXjIAJ", + "FirstName": "John", + "LastName": "Rambo", }, - 'sobject': { - 'Id': '004D000000KnaXjIAJ', - 'FirstName': 'John', - 'LastName': 'Rambo', - } }, ] @@ -176,14 +163,15 @@ def salesforce_server_port(): @pytest.fixture def salesforce_server_uri(salesforce_server_port): - return 'http://localhost:{}/cometd/37.0'.format(salesforce_server_port) + return "http://localhost:{}/cometd/37.0".format(salesforce_server_port) @pytest.fixture def login(salesforce_server_uri): def patched_login(obj): obj.server_uri = salesforce_server_uri - with patch.object(StreamingClient, 'login', patched_login) as login: + + with patch.object(StreamingClient, "login", patched_login) as login: yield login @@ -205,28 +193,22 @@ def make_salesforce_server( """ def _make(responses): - class CometdServer(object): name = "cometd" - @http('POST', "/cometd/37.0") + @http("POST", "/cometd/37.0") def handle(self, request): - tracker.request( - json.loads(request.get_data().decode(encoding='UTF-8'))) + tracker.request(json.loads(request.get_data().decode(encoding="UTF-8"))) try: return 200, json.dumps(responses.pop(0)) except IndexError: waiter.send() sleep(0.1) - no_events_to_deliver = [ - message_maker.make_connect_response()] + no_events_to_deliver = [message_maker.make_connect_response()] return (200, json.dumps(no_events_to_deliver)) - config = { - 'WEB_SERVER_ADDRESS': 'localhost:{}'.format( - salesforce_server_port) - } + config = {"WEB_SERVER_ADDRESS": "localhost:{}".format(salesforce_server_port)} container = container_factory(CometdServer, config) return container @@ -235,9 +217,7 @@ def handle(self, request): @pytest.fixture -def run_services( - container_factory, config, login, make_salesforce_server, waiter -): +def run_services(container_factory, config, login, make_salesforce_server, waiter): """ Returns services runner """ @@ -274,20 +254,19 @@ def salesforce_api_server(salesforce_server_uri): PushTopic declaration scenario. """ - with patch('simple_salesforce.api.SalesforceLogin') as SalesforceLogin: - SalesforceLogin.return_value = 'session', 'abc.salesforce.com' + with patch("simple_salesforce.api.SalesforceLogin") as SalesforceLogin: + SalesforceLogin.return_value = "session", "abc.salesforce.com" with requests_mock.Mocker() as mocked_requests: yield mocked_requests def test_subscribe(message_maker, notifications, run_services, tracker): - class Service: - name = 'example-service' + name = "example-service" - @subscribe('/topic/AccountUpdates') - @subscribe('/topic/ContactUpdates') + @subscribe("/topic/AccountUpdates") + @subscribe("/topic/ContactUpdates") def handle_event(self, topic, event): tracker.handle_event(topic, event) @@ -296,24 +275,21 @@ def handle_event(self, topic, event): responses = [ [message_maker.make_handshake_response()], [ - message_maker.make_subscribe_response( - subscription='/topic/AccountUpdates'), - message_maker.make_subscribe_response( - subscription='/topic/ContactUpdates'), + message_maker.make_subscribe_response(subscription="/topic/AccountUpdates"), + message_maker.make_subscribe_response(subscription="/topic/ContactUpdates"), ], [ message_maker.make_connect_response( - advice={'reconnect': Reconnection.retry.value}), + advice={"reconnect": Reconnection.retry.value} + ) ], # two events to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactUpdates', - data=notification_one, + channel="/topic/ContactUpdates", data=notification_one ), message_maker.make_event_delivery_message( - channel='/topic/AccountUpdates', - data=notification_two, + channel="/topic/AccountUpdates", data=notification_two ), ], # no event to deliver within server timeout @@ -321,9 +297,8 @@ def handle_event(self, topic, event): # one event to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactUpdates', - data=notification_three, - ), + channel="/topic/ContactUpdates", data=notification_three + ) ], ] @@ -332,14 +307,10 @@ def handle_event(self, topic, event): handshake, subscriptions = tracker.request.call_args_list[:2] connect = tracker.request.call_args_list[2:] - assert handshake == call( - [message_maker.make_handshake_request(id=1)]) + assert handshake == call([message_maker.make_handshake_request(id=1)]) - topics = [ - message.pop('subscription') for message in subscriptions[0][0] - ] - assert set(topics) == set( - ['/topic/AccountUpdates', '/topic/ContactUpdates']) + topics = [message.pop("subscription") for message in subscriptions[0][0]] + assert set(topics) == set(["/topic/AccountUpdates", "/topic/ContactUpdates"]) assert connect == [ call([message_maker.make_connect_request(id=4)]), @@ -350,19 +321,23 @@ def handle_event(self, topic, event): ] expected_event_handling = [ - call('/topic/ContactUpdates', notification_one), - call('/topic/AccountUpdates', notification_two), - call('/topic/ContactUpdates', notification_three), + call("/topic/ContactUpdates", notification_one), + call("/topic/AccountUpdates", notification_two), + call("/topic/ContactUpdates", notification_three), ] assert tracker.handle_event.call_args_list == expected_event_handling def test_handle_notification( - message_maker, notifications, run_services, tracker, - salesforce_api_server, salesforce_server_uri + message_maker, + notifications, + run_services, + tracker, + salesforce_api_server, + salesforce_server_uri, ): - salesforce_api_server.get(requests_mock.ANY, json={'totalSize': 0}) + salesforce_api_server.get(requests_mock.ANY, json={"totalSize": 0}) salesforce_api_server.post(requests_mock.ANY, json={}) # un-mock Salesforce Streaming API calls making the mocker to let # streaming requests reach the testing streaming API server: @@ -370,17 +345,17 @@ def test_handle_notification( class Service: - name = 'example-service' + name = "example-service" - @handle_notification('AccountUpdates') + @handle_notification("AccountUpdates") @handle_notification( - 'ContactUpdates', - query='SELECT ...', + "ContactUpdates", + query="SELECT ...", notify_for_fields=constants.NotifyForFields.referenced, notify_for_operation_create=False, notify_for_operation_update=True, notify_for_operation_delete=False, - notify_for_operation_undelete=False + notify_for_operation_undelete=False, ) def handle_notification(self, topic, notification): tracker.handle_notification(topic, notification) @@ -390,24 +365,21 @@ def handle_notification(self, topic, notification): responses = [ [message_maker.make_handshake_response()], [ - message_maker.make_subscribe_response( - subscription='/topic/AccountUpdates'), - message_maker.make_subscribe_response( - subscription='/topic/ContactUpdates'), + message_maker.make_subscribe_response(subscription="/topic/AccountUpdates"), + message_maker.make_subscribe_response(subscription="/topic/ContactUpdates"), ], [ message_maker.make_connect_response( - advice={'reconnect': Reconnection.retry.value}), + advice={"reconnect": Reconnection.retry.value} + ) ], # two notifications to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactUpdates', - data=notification_one, + channel="/topic/ContactUpdates", data=notification_one ), message_maker.make_event_delivery_message( - channel='/topic/AccountUpdates', - data=notification_two, + channel="/topic/AccountUpdates", data=notification_two ), ], # no notification to deliver within server timeout @@ -415,9 +387,8 @@ def handle_notification(self, topic, notification): # one notification to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactUpdates', - data=notification_three, - ), + channel="/topic/ContactUpdates", data=notification_three + ) ], ] @@ -426,14 +397,10 @@ def handle_notification(self, topic, notification): handshake, subscriptions = tracker.request.call_args_list[:2] connect = tracker.request.call_args_list[2:] - assert handshake == call( - [message_maker.make_handshake_request(id=1)]) + assert handshake == call([message_maker.make_handshake_request(id=1)]) - topics = [ - message.pop('subscription') for message in subscriptions[0][0] - ] - assert set(topics) == set( - ['/topic/AccountUpdates', '/topic/ContactUpdates']) + topics = [message.pop("subscription") for message in subscriptions[0][0]] + assert set(topics) == set(["/topic/AccountUpdates", "/topic/ContactUpdates"]) assert connect == [ call([message_maker.make_connect_request(id=4)]), @@ -444,46 +411,49 @@ def handle_notification(self, topic, notification): ] expected_notification_handling = [ - call('ContactUpdates', notification_one), - call('AccountUpdates', notification_two), - call('ContactUpdates', notification_three), + call("ContactUpdates", notification_one), + call("AccountUpdates", notification_two), + call("ContactUpdates", notification_three), ] - assert ( - tracker.handle_notification.call_args_list == - expected_notification_handling) + assert tracker.handle_notification.call_args_list == expected_notification_handling - get_push_topic, create_push_topic = ( - salesforce_api_server.request_history[:2]) + get_push_topic, create_push_topic = salesforce_api_server.request_history[:2] - assert get_push_topic.method == 'GET' - assert create_push_topic.method == 'POST' + assert get_push_topic.method == "GET" + assert create_push_topic.method == "POST" push_topic_definition = create_push_topic.json() - assert push_topic_definition['Name'] == 'ContactUpdates' - assert push_topic_definition['Query'] == 'SELECT ...' + assert push_topic_definition["Name"] == "ContactUpdates" + assert push_topic_definition["Query"] == "SELECT ..." assert ( - push_topic_definition['NotifyForFields'] == - constants.NotifyForFields.referenced.value) - assert push_topic_definition['NotifyForOperationCreate'] is False - assert push_topic_definition['NotifyForOperationUpdate'] is True - assert push_topic_definition['NotifyForOperationDelete'] is False - assert push_topic_definition['NotifyForOperationUndelete'] is False + push_topic_definition["NotifyForFields"] + == constants.NotifyForFields.referenced.value + ) + assert push_topic_definition["NotifyForOperationCreate"] is False + assert push_topic_definition["NotifyForOperationUpdate"] is True + assert push_topic_definition["NotifyForOperationDelete"] is False + assert push_topic_definition["NotifyForOperationUndelete"] is False def test_handle_sobject_notification( - message_maker, notifications, run_services, tracker, - salesforce_api_server, salesforce_server_uri + message_maker, + notifications, + run_services, + tracker, + salesforce_api_server, + salesforce_server_uri, ): salesforce_api_server.get( requests_mock.ANY, [ # getting current user ID - {'json': {'totalSize': 1, 'records': [{'Id': '00..A0'}]}}, + {"json": {"totalSize": 1, "records": [{"Id": "00..A0"}]}}, # getting the PushTopic - {'json': {'totalSize': 0}} - ]) + {"json": {"totalSize": 0}}, + ], + ) salesforce_api_server.post(requests_mock.ANY, json={}) # un-mock Salesforce Streaming API calls making the mocker to let # streaming requests reach the testing streaming API server: @@ -491,48 +461,42 @@ def test_handle_sobject_notification( class Service: - name = 'example-service' + name = "example-service" - @handle_sobject_notification('Contact', declare=False) + @handle_sobject_notification("Contact", declare=False) @handle_sobject_notification( - sobject_type='Contact', - record_type='Student', + sobject_type="Contact", + record_type="Student", exclude_current_user=False, notify_for_fields=constants.NotifyForFields.referenced, notify_for_operation_create=False, notify_for_operation_update=True, notify_for_operation_delete=False, - notify_for_operation_undelete=False + notify_for_operation_undelete=False, ) - def handle_notification( - self, sobject_type, record_type, notification - ): - tracker.handle_notification( - sobject_type, record_type, notification) + def handle_notification(self, sobject_type, record_type, notification): + tracker.handle_notification(sobject_type, record_type, notification) notification_one, notification_two, notification_three = notifications responses = [ [message_maker.make_handshake_response()], [ - message_maker.make_subscribe_response( - subscription='/topic/Contact'), - message_maker.make_subscribe_response( - subscription='/topic/ContactStudent'), + message_maker.make_subscribe_response(subscription="/topic/Contact"), + message_maker.make_subscribe_response(subscription="/topic/ContactStudent"), ], [ message_maker.make_connect_response( - advice={'reconnect': Reconnection.retry.value}), + advice={"reconnect": Reconnection.retry.value} + ) ], # two notifications to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactStudent', - data=notification_one, + channel="/topic/ContactStudent", data=notification_one ), message_maker.make_event_delivery_message( - channel='/topic/Contact', - data=notification_two, + channel="/topic/Contact", data=notification_two ), ], # no notification to deliver within server timeout @@ -540,9 +504,8 @@ def handle_notification( # one notification to deliver [ message_maker.make_event_delivery_message( - channel='/topic/ContactStudent', - data=notification_three, - ), + channel="/topic/ContactStudent", data=notification_three + ) ], ] @@ -551,14 +514,10 @@ def handle_notification( handshake, subscriptions = tracker.request.call_args_list[:2] connect = tracker.request.call_args_list[2:] - assert handshake == call( - [message_maker.make_handshake_request(id=1)]) + assert handshake == call([message_maker.make_handshake_request(id=1)]) - topics = [ - message.pop('subscription') for message in subscriptions[0][0] - ] - assert set(topics) == set( - ['/topic/Contact', '/topic/ContactStudent']) + topics = [message.pop("subscription") for message in subscriptions[0][0]] + assert set(topics) == set(["/topic/Contact", "/topic/ContactStudent"]) assert connect == [ call([message_maker.make_connect_request(id=4)]), @@ -569,29 +528,28 @@ def handle_notification( ] expected_notification_handling = [ - call('Contact', 'Student', notification_one), - call('Contact', None, notification_two), - call('Contact', 'Student', notification_three), + call("Contact", "Student", notification_one), + call("Contact", None, notification_two), + call("Contact", "Student", notification_three), ] - assert ( - tracker.handle_notification.call_args_list == - expected_notification_handling) + assert tracker.handle_notification.call_args_list == expected_notification_handling - get_user_id, get_push_topic, create_push_topic = ( - salesforce_api_server.request_history[:3]) + requests = salesforce_api_server.request_history[:3] + get_user_id, get_push_topic, create_push_topic = requests - assert get_user_id.method == 'GET' - assert get_push_topic.method == 'GET' - assert create_push_topic.method == 'POST' + assert get_user_id.method == "GET" + assert get_push_topic.method == "GET" + assert create_push_topic.method == "POST" push_topic_definition = create_push_topic.json() - assert push_topic_definition['Name'] == 'ContactStudent' - assert push_topic_definition['Query'].startswith('SELECT') + assert push_topic_definition["Name"] == "ContactStudent" + assert push_topic_definition["Query"].startswith("SELECT") assert ( - push_topic_definition['NotifyForFields'] == - constants.NotifyForFields.referenced.value) - assert push_topic_definition['NotifyForOperationCreate'] is False - assert push_topic_definition['NotifyForOperationUpdate'] is True - assert push_topic_definition['NotifyForOperationDelete'] is False - assert push_topic_definition['NotifyForOperationUndelete'] is False + push_topic_definition["NotifyForFields"] + == constants.NotifyForFields.referenced.value + ) + assert push_topic_definition["NotifyForOperationCreate"] is False + assert push_topic_definition["NotifyForOperationUpdate"] is True + assert push_topic_definition["NotifyForOperationDelete"] is False + assert push_topic_definition["NotifyForOperationUndelete"] is False diff --git a/tests/streaming/test_unit.py b/tests/streaming/test_unit.py index b1f1295..d0a3579 100644 --- a/tests/streaming/test_unit.py +++ b/tests/streaming/test_unit.py @@ -1,8 +1,8 @@ import collections -from mock import call, Mock, patch -from nameko.exceptions import ConfigurationError import pytest +from mock import Mock, call, patch +from nameko.exceptions import ConfigurationError from nameko_salesforce import constants from nameko_salesforce.streaming.client import ( @@ -15,7 +15,7 @@ @pytest.fixture def container(config): - container = collections.namedtuple('container', ('config',)) + container = collections.namedtuple("container", ("config",)) container.config = config return container @@ -35,18 +35,19 @@ class TestStreamingClientSetup: """ def test_setup(self, client, config): - assert client.version == config['SALESFORCE']['BAYEUX_VERSION'] + assert client.version == config["SALESFORCE"]["BAYEUX_VERSION"] assert client.minimum_version == ( - config['SALESFORCE']['BAYEUX_MINIMUM_VERSION']) - assert client.api_version == config['SALESFORCE']['API_VERSION'] - assert client.username == config['SALESFORCE']['USERNAME'] - assert client.password == config['SALESFORCE']['PASSWORD'] - assert client.security_token == config['SALESFORCE']['SECURITY_TOKEN'] - assert client.sandbox == config['SALESFORCE']['SANDBOX'] + config["SALESFORCE"]["BAYEUX_MINIMUM_VERSION"] + ) + assert client.api_version == config["SALESFORCE"]["API_VERSION"] + assert client.username == config["SALESFORCE"]["USERNAME"] + assert client.password == config["SALESFORCE"]["PASSWORD"] + assert client.security_token == config["SALESFORCE"]["SECURITY_TOKEN"] + assert client.sandbox == config["SALESFORCE"]["SANDBOX"] def test_setup_main_config_key_missing(self, container): - container.config.pop('SALESFORCE') + container.config.pop("SALESFORCE") client = StreamingClient() client.container = container @@ -54,16 +55,16 @@ def test_setup_main_config_key_missing(self, container): with pytest.raises(ConfigurationError) as exc: client.setup() - assert str(exc.value) == '`SALESFORCE` config key not found' + assert str(exc.value) == "`SALESFORCE` config key not found" @pytest.mark.parametrize( - ('attr', 'key', 'expected_default'), + ("attr", "key", "expected_default"), ( - ('version', 'BAYEUX_VERSION', '1.0'), - ('minimum_version', 'BAYEUX_MINIMUM_VERSION', '1.0'), - ('api_version', 'API_VERSION', '37.0'), - ('replay_enabled', 'PUSHTOPIC_REPLAY_ENABLED', False), - ('replay_storage_ttl', 'PUSHTOPIC_REPLAY_TTL', 60 * 60 * 12), + ("version", "BAYEUX_VERSION", "1.0"), + ("minimum_version", "BAYEUX_MINIMUM_VERSION", "1.0"), + ("api_version", "API_VERSION", "37.0"), + ("replay_enabled", "PUSHTOPIC_REPLAY_ENABLED", False), + ("replay_storage_ttl", "PUSHTOPIC_REPLAY_TTL", 60 * 60 * 12), ), ) def test_setup_defaults(self, attr, key, expected_default, container): @@ -77,7 +78,7 @@ def test_setup_defaults(self, attr, key, expected_default, container): assert getattr(client, attr) == expected_default @pytest.mark.parametrize( - 'key', ('USERNAME', 'PASSWORD', 'SECURITY_TOKEN', 'SANDBOX') + "key", ("USERNAME", "PASSWORD", "SECURITY_TOKEN", "SANDBOX") ) def test_setup_config_keys_missing(self, container, key): @@ -89,15 +90,13 @@ def test_setup_config_keys_missing(self, container, key): with pytest.raises(ConfigurationError) as exc: client.setup() - expected_error = ( - '`{}` configuration does not contain mandatory `{}` key' - .format(constants.CONFIG_KEY, key)) - assert str(exc.value) == expected_error + expected_error = "`{}` configuration does not contain mandatory `{}` key" + assert str(exc.value) == expected_error.format(constants.CONFIG_KEY, key) def test_setup_replay_enabled(self, container, redis_uri): config = container.config[constants.CONFIG_KEY] - config['PUSHTOPIC_REPLAY_ENABLED'] = True + config["PUSHTOPIC_REPLAY_ENABLED"] = True client = StreamingClient() client.container = container @@ -106,13 +105,13 @@ def test_setup_replay_enabled(self, container, redis_uri): client.setup() expected_error = ( - '`{}` must have `PUSHTOPIC_REPLAY_REDIS_URI` defined if ' - '`PUSHTOPIC_REPLAY_ENABLED` is set to `True`' - .format(constants.CONFIG_KEY)) + "`{}` must have `PUSHTOPIC_REPLAY_REDIS_URI` defined if " + "`PUSHTOPIC_REPLAY_ENABLED` is set to `True`".format(constants.CONFIG_KEY) + ) assert str(exc.value) == expected_error - config['PUSHTOPIC_REPLAY_REDIS_URI'] = redis_uri - config['PUSHTOPIC_REPLAY_TTL'] = 3600 + config["PUSHTOPIC_REPLAY_REDIS_URI"] = redis_uri + config["PUSHTOPIC_REPLAY_TTL"] = 3600 client.setup() @@ -128,17 +127,15 @@ class TestStreamingClientAuthentication: @pytest.fixture def access_token(self): - return '*********' + return "*********" @pytest.fixture def server_host(self): - return 'some.salesforce.server' + return "some.salesforce.server" @pytest.fixture def login(self, access_token, server_host): - with patch( - 'nameko_salesforce.streaming.client.SalesforceLogin' - ) as login: + with patch("nameko_salesforce.streaming.client.SalesforceLogin") as login: login.return_value = (access_token, server_host) yield login @@ -147,25 +144,19 @@ def test_login(self, access_token, client, config, login): client.login() assert client.access_token == access_token - assert ( - client.server_uri == - 'https://some.salesforce.server/cometd/37.0' - ) - assert ( - login.call_args == - call( - session=None, - username=client.username, - password=client.password, - security_token=client.security_token, - sandbox=client.sandbox, - sf_version=client.api_version, - ) + assert client.server_uri == "https://some.salesforce.server/cometd/37.0" + assert login.call_args == call( + session=None, + username=client.username, + password=client.password, + security_token=client.security_token, + sandbox=client.sandbox, + sf_version=client.api_version, ) def test_get_authorisation(self, client, access_token): client.access_token = access_token - assert client.get_authorisation() == ('Bearer', access_token) + assert client.get_authorisation() == ("Bearer", access_token) class TestStreamingClientReplayStorage: @@ -176,68 +167,65 @@ class TestStreamingClientReplayStorage: @pytest.fixture def config(self, config, redis_uri): - config['SALESFORCE']['PUSHTOPIC_REPLAY_ENABLED'] = True - config['SALESFORCE']['PUSHTOPIC_REPLAY_REDIS_URI'] = redis_uri - config['SALESFORCE']['PUSHTOPIC_REPLAY_TTL'] = 1 + config["SALESFORCE"]["PUSHTOPIC_REPLAY_ENABLED"] = True + config["SALESFORCE"]["PUSHTOPIC_REPLAY_REDIS_URI"] = redis_uri + config["SALESFORCE"]["PUSHTOPIC_REPLAY_TTL"] = 1 return config def test_set_replay_id(self, client, redis_client): - channel_name = '/topic/number/one' + channel_name = "/topic/number/one" client.set_replay_id(channel_name, 11) - replay_id = int( - redis_client.get('salesforce:replay_id:/topic/number/one')) + replay_id = int(redis_client.get("salesforce:replay_id:/topic/number/one")) assert replay_id == 11 client.set_replay_id(channel_name, 22) - replay_id = int( - redis_client.get('salesforce:replay_id:/topic/number/one')) + replay_id = int(redis_client.get("salesforce:replay_id:/topic/number/one")) assert replay_id == 22 def test_get_replay_id(self, client, redis_client): - channel_name = '/topic/number/one' + channel_name = "/topic/number/one" assert client.get_replay_id(channel_name) is None - redis_client.set( - 'salesforce:replay_id:/topic/number/one', 11) + redis_client.set("salesforce:replay_id:/topic/number/one", 11) assert client.get_replay_id(channel_name) == 11 - @patch.object(StreamingClient, 'send_and_handle') + @patch.object(StreamingClient, "send_and_handle") def test_subscribe(self, send_and_handle, client, redis_client): - client._subscriptions = ['/topic/spam', '/topic/egg', '/topic/ham'] + client._subscriptions = ["/topic/spam", "/topic/egg", "/topic/ham"] client.client_id = Mock() replay_id = 11 - redis_client.set('salesforce:replay_id:/topic/egg', replay_id) + redis_client.set("salesforce:replay_id:/topic/egg", replay_id) client.subscribe() expected_subscriptions = [ { - 'id': 1, - 'clientId': client.client_id, - 'channel': '/meta/subscribe', - 'subscription': '/topic/spam', + "id": 1, + "clientId": client.client_id, + "channel": "/meta/subscribe", + "subscription": "/topic/spam", }, { - 'id': 2, - 'clientId': client.client_id, - 'channel': '/meta/subscribe', - 'subscription': '/topic/egg', - 'ext': {'replay': {'/topic/egg': 11}}, # replay extension + "id": 2, + "clientId": client.client_id, + "channel": "/meta/subscribe", + "subscription": "/topic/egg", + "ext": {"replay": {"/topic/egg": 11}}, # replay extension }, { - 'id': 3, - 'clientId': client.client_id, - 'channel': '/meta/subscribe', - 'subscription': '/topic/ham', + "id": 3, + "clientId": client.client_id, + "channel": "/meta/subscribe", + "subscription": "/topic/ham", }, ] assert send_and_handle.call_args == call(expected_subscriptions) @@ -251,11 +239,11 @@ class TestMessageHandler: @pytest.fixture def channel_name(self): - return '/topic/InvoiceStatementUpdates' + return "/topic/InvoiceStatementUpdates" @pytest.fixture def handler(self, channel_name): - with patch.object(StreamingClient, 'set_replay_id'): + with patch.object(StreamingClient, "set_replay_id"): handler = MessageHandler(channel_name) handler.container = Mock() handler.client.replay_storage = Mock() @@ -266,21 +254,20 @@ def test_handle_message(self, handler, channel_name): """ Test that handle_message parses and passes the reply_id """ - replay_id = '001122' - message = {'sobject': 'spam', 'event': {'replayId': replay_id}} + replay_id = "001122" + message = {"sobject": "spam", "event": {"replayId": replay_id}} handler.handle_message(message) call_args, call_kwargs = handler.container.spawn_worker.call_args assert call_args == (handler, (channel_name, message), {}) assert ( - call_kwargs['context_data'][constants.CLIENT_ID_CONTEXT_KEY] == - handler.client.client_id) - assert ( - call_kwargs['context_data'][constants.REPLAY_ID_CONTEXT_KEY] == - replay_id) - assert call_kwargs['handle_result'].func == handler.handle_result - assert call_kwargs['handle_result'].args == (replay_id,) + call_kwargs["context_data"][constants.CLIENT_ID_CONTEXT_KEY] + == handler.client.client_id + ) + assert call_kwargs["context_data"][constants.REPLAY_ID_CONTEXT_KEY] == replay_id + assert call_kwargs["handle_result"].func == handler.handle_result + assert call_kwargs["handle_result"].args == (replay_id,) class TestNotificationHandler: @@ -291,48 +278,49 @@ class TestNotificationHandler: @pytest.fixture def make_handler(self): - with patch.object(StreamingClient, 'set_replay_id'): + with patch.object(StreamingClient, "set_replay_id"): + def _make(*args, **kwargs): handler = NotificationHandler(*args, **kwargs) handler.container = Mock() handler.client.replay_storage = Mock() handler.client.client_id = Mock() return handler + yield _make def test_channel_name(self, make_handler): - handler = make_handler('Contact') - assert handler.channel_name == '/topic/Contact' + handler = make_handler("Contact") + assert handler.channel_name == "/topic/Contact" def test_handle_message(self, make_handler): """ Test that handle_message parses and passes the reply_id """ - name = 'Contact' + name = "Contact" handler = make_handler(name) - replay_id = '001122' - message = {'sobject': 'spam', 'event': {'replayId': replay_id}} + replay_id = "001122" + message = {"sobject": "spam", "event": {"replayId": replay_id}} handler.handle_message(message) call_args, call_kwargs = handler.container.spawn_worker.call_args assert call_args == (handler, (name, message), {}) assert ( - call_kwargs['context_data'][constants.CLIENT_ID_CONTEXT_KEY] == - handler.client.client_id) - assert ( - call_kwargs['context_data'][constants.REPLAY_ID_CONTEXT_KEY] == - replay_id) - assert call_kwargs['handle_result'].func == handler.handle_result - assert call_kwargs['handle_result'].args == (replay_id,) + call_kwargs["context_data"][constants.CLIENT_ID_CONTEXT_KEY] + == handler.client.client_id + ) + assert call_kwargs["context_data"][constants.REPLAY_ID_CONTEXT_KEY] == replay_id + assert call_kwargs["handle_result"].func == handler.handle_result + assert call_kwargs["handle_result"].args == (replay_id,) def test_declare_push_topic_no_query(self, make_handler): """ Test that no push topic is declared there is no query is provided """ - name = 'Contact' + name = "Contact" handler = make_handler(name) @@ -346,8 +334,8 @@ def test_declare_push_topic_minimal_configuration(self, make_handler): """ Test default push topic declaration """ - name = 'Contact' - query = 'SELECT ...' + name = "Contact" + query = "SELECT ..." handler = make_handler(name, query) @@ -362,14 +350,15 @@ def test_declare_push_topic_minimal_configuration(self, make_handler): notify_for_operation_create=True, notify_for_operation_update=True, notify_for_operation_delete=True, - notify_for_operation_undelete=True) + notify_for_operation_undelete=True, + ) def test_declare_push_topic_full_configuration(self, make_handler): """ Test fully configured push topic declaration """ - name = 'Contact' - query = 'SELECT ...' + name = "Contact" + query = "SELECT ..." notify_for_fields = constants.NotifyForFields.referenced notify_for_operation_create = False notify_for_operation_update = False @@ -383,7 +372,8 @@ def test_declare_push_topic_full_configuration(self, make_handler): notify_for_operation_create=notify_for_operation_create, notify_for_operation_update=notify_for_operation_update, notify_for_operation_delete=notify_for_operation_delete, - notify_for_operation_undelete=notify_for_operation_undelete) + notify_for_operation_undelete=notify_for_operation_undelete, + ) api_client = Mock() @@ -396,7 +386,8 @@ def test_declare_push_topic_full_configuration(self, make_handler): notify_for_operation_create=notify_for_operation_create, notify_for_operation_update=notify_for_operation_update, notify_for_operation_delete=notify_for_operation_delete, - notify_for_operation_undelete=notify_for_operation_undelete) + notify_for_operation_undelete=notify_for_operation_undelete, + ) class TestSobjectNotificationHandler: @@ -407,21 +398,23 @@ class TestSobjectNotificationHandler: @pytest.fixture def make_handler(self): - with patch.object(StreamingClient, 'set_replay_id'): + with patch.object(StreamingClient, "set_replay_id"): + def _make(*args, **kwargs): handler = SobjectNotificationHandler(*args, **kwargs) handler.container = Mock() handler.client.replay_storage = Mock() handler.client.id = Mock() return handler + yield _make @pytest.mark.parametrize( - ('sobject_type', 'record_type', 'expected_channel_name'), + ("sobject_type", "record_type", "expected_channel_name"), ( - ('Contact', None, '/topic/Contact'), - ('Contact', 'Student', '/topic/ContactStudent'), - ) + ("Contact", None, "/topic/Contact"), + ("Contact", "Student", "/topic/ContactStudent"), + ), ) def test_channel_name( self, make_handler, sobject_type, record_type, expected_channel_name @@ -433,33 +426,31 @@ def test_handle_message(self, make_handler): """ Test that handle_message parses and passes the reply_id """ - sobject_type = 'Contact' - record_type = 'Student' + sobject_type = "Contact" + record_type = "Student" handler = make_handler(sobject_type, record_type) - replay_id = '001122' - message = {'sobject': 'spam', 'event': {'replayId': replay_id}} + replay_id = "001122" + message = {"sobject": "spam", "event": {"replayId": replay_id}} handler.handle_message(message) call_args, call_kwargs = handler.container.spawn_worker.call_args - assert call_args == ( - handler, (sobject_type, record_type, message), {}) + assert call_args == (handler, (sobject_type, record_type, message), {}) assert ( - call_kwargs['context_data'][constants.CLIENT_ID_CONTEXT_KEY] == - handler.client.client_id) - assert ( - call_kwargs['context_data'][constants.REPLAY_ID_CONTEXT_KEY] == - replay_id) - assert call_kwargs['handle_result'].func == handler.handle_result - assert call_kwargs['handle_result'].args == (replay_id,) + call_kwargs["context_data"][constants.CLIENT_ID_CONTEXT_KEY] + == handler.client.client_id + ) + assert call_kwargs["context_data"][constants.REPLAY_ID_CONTEXT_KEY] == replay_id + assert call_kwargs["handle_result"].func == handler.handle_result + assert call_kwargs["handle_result"].args == (replay_id,) def test_declare_push_topic_declaration_disabled(self, make_handler): """ Test that no push topic is declared if switched off """ - sobject_type = 'Contact' + sobject_type = "Contact" handler = make_handler(sobject_type, declare=False) @@ -473,7 +464,7 @@ def test_declare_push_topic_minimal_configuration(self, make_handler): """ Test default push topic declaration """ - sobject_type = 'Contact' + sobject_type = "Contact" handler = make_handler(sobject_type) @@ -489,14 +480,15 @@ def test_declare_push_topic_minimal_configuration(self, make_handler): notify_for_operation_create=True, notify_for_operation_update=True, notify_for_operation_delete=True, - notify_for_operation_undelete=True) + notify_for_operation_undelete=True, + ) def test_declare_push_topic_full_configuration(self, make_handler): """ Test fully configured push topic declaration """ - sobject_type = 'Contact' - record_type = 'Student' + sobject_type = "Contact" + record_type = "Student" exclude_current_user = False notify_for_fields = constants.NotifyForFields.referenced notify_for_operation_create = False @@ -512,7 +504,8 @@ def test_declare_push_topic_full_configuration(self, make_handler): notify_for_operation_create=notify_for_operation_create, notify_for_operation_update=notify_for_operation_update, notify_for_operation_delete=notify_for_operation_delete, - notify_for_operation_undelete=notify_for_operation_undelete) + notify_for_operation_undelete=notify_for_operation_undelete, + ) api_client = Mock() @@ -526,7 +519,8 @@ def test_declare_push_topic_full_configuration(self, make_handler): notify_for_operation_create=notify_for_operation_create, notify_for_operation_update=notify_for_operation_update, notify_for_operation_delete=notify_for_operation_delete, - notify_for_operation_undelete=notify_for_operation_undelete) + notify_for_operation_undelete=notify_for_operation_undelete, + ) class TestMessageHandlers: @@ -538,15 +532,14 @@ class TestMessageHandlers: @pytest.fixture def channel_name(self): - return '/topic/InvoiceStatementUpdates' + return "/topic/InvoiceStatementUpdates" - @pytest.fixture(params=( - MessageHandler, - NotificationHandler, - SobjectNotificationHandler)) + @pytest.fixture( + params=(MessageHandler, NotificationHandler, SobjectNotificationHandler) + ) def handler(self, request, channel_name): handler_cls = request.param - with patch.object(StreamingClient, 'set_replay_id'): + with patch.object(StreamingClient, "set_replay_id"): handler = handler_cls(channel_name) handler.container = Mock() handler.client.replay_storage = Mock() @@ -560,13 +553,14 @@ def test_handle_result_success_replay_disabled(self, handler): handler.client.replay_enabled = False - replay_id = '001122' + replay_id = "001122" worker_ctx, result = Mock(), Mock() exc_info = None - assert ( - handler.handle_result(replay_id, worker_ctx, result, exc_info) == - (result, exc_info)) + assert handler.handle_result(replay_id, worker_ctx, result, exc_info) == ( + result, + exc_info, + ) assert handler.client.set_replay_id.call_count == 0 def test_handle_result_success_replay_enabled(self, handler): @@ -577,16 +571,17 @@ def test_handle_result_success_replay_enabled(self, handler): handler.client.replay_enabled = True - replay_id = '001122' + replay_id = "001122" worker_ctx, result = Mock(), Mock() exc_info = None - assert ( - handler.handle_result(replay_id, worker_ctx, result, exc_info) == - (result, exc_info)) - assert ( - handler.client.set_replay_id.call_args == - call(handler.channel_name, replay_id)) + assert handler.handle_result(replay_id, worker_ctx, result, exc_info) == ( + result, + exc_info, + ) + assert handler.client.set_replay_id.call_args == call( + handler.channel_name, replay_id + ) def test_handle_result_failure_replay_disabled(self, handler): """ Test that handle_result doesn't set the replay ID @@ -596,13 +591,14 @@ def test_handle_result_failure_replay_disabled(self, handler): handler.client.replay_enabled = False - replay_id = '001122' + replay_id = "001122" worker_ctx, result = Mock(), Mock() exc_info = Mock() # an exception raised inside the worker - assert ( - handler.handle_result(replay_id, worker_ctx, result, exc_info) == - (result, exc_info)) + assert handler.handle_result(replay_id, worker_ctx, result, exc_info) == ( + result, + exc_info, + ) assert handler.client.set_replay_id.call_count == 0 def test_handle_result_failure_replay_enabled(self, handler): @@ -613,11 +609,12 @@ def test_handle_result_failure_replay_enabled(self, handler): handler.client.replay_enabled = True - replay_id = '001122' + replay_id = "001122" worker_ctx, result = Mock(), Mock() exc_info = Mock() # an exception raised inside the worker - assert ( - handler.handle_result(replay_id, worker_ctx, result, exc_info) == - (result, exc_info)) + assert handler.handle_result(replay_id, worker_ctx, result, exc_info) == ( + result, + exc_info, + ) assert handler.client.set_replay_id.call_count == 0 From 53bb9b95260913031be858c7543e8446ac001579 Mon Sep 17 00:00:00 2001 From: Ondrej Kohout Date: Wed, 1 May 2019 08:05:29 +0200 Subject: [PATCH 2/5] Add pre-commit static checks to CI --- .pre-commit-config.yaml | 20 ++++++++++++++++++++ .travis.yml | 7 ++++++- Makefile | 8 ++++---- setup.cfg | 17 +++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..af309da --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v1.3.0 + hooks: + - id: trailing-whitespace + - id: check-ast + - id: check-merge-conflict + - id: flake8 +- repo: https://github.com/asottile/seed-isort-config + rev: v1.8.0 + hooks: + - id: seed-isort-config +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.17 + hooks: + - id: isort +- repo: https://github.com/ambv/black + rev: 19.3b0 + hooks: + - id: black diff --git a/.travis.yml b/.travis.yml index 85dc12d..3bf1d8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ services: - redis-server stages: + - static - test jobs: @@ -26,6 +27,10 @@ jobs: env: DEPS="nameko>=2.12.0" - python: 3.5 env: DEPS="nameko>=2.12.0" + stage: static + install: pip install pre-commit + script: make static + env: matrix: allow_failures: @@ -38,7 +43,7 @@ install: - pip install -U $DEPS script: - - make test + - make pytest deploy: diff --git a/Makefile b/Makefile index 72e90cf..7aaf0a2 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -test: flake8 pylint pytest +test: static pylint pytest -flake8: - flake8 nameko_salesforce tests +static: + pre-commit run --all-files pylint: - pylint nameko_salesforce -E + pylint nameko_salesforce --disable=R,C,W0221 pytest: coverage run --concurrency=eventlet --source nameko_salesforce --branch -m pytest tests diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3fd8415 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,17 @@ +[bdist_wheel] +universal = 1 + +[isort] +line_length=88 +known_first_party=nameko_salesforce +known_third_party=cachetools,eventlet,mock,nameko,nameko_bayeux_client,pytest,redis,requests,requests_mock,setuptools,simple_salesforce,sphinx_rtd_theme,urllib3 +multi_line_output=3 +indent=' ' +include_trailing_comma=true +forced_separate=test +default_section=THIRDPARTY +lines_after_imports=2 +skip=.tox,.git + +[flake8] +max-line-length = 88 From fee1d656942fadc4786ee8142f61fa65b7ebf89d Mon Sep 17 00:00:00 2001 From: Ondrej Kohout Date: Wed, 1 May 2019 08:48:10 +0200 Subject: [PATCH 3/5] Forgotten CI config entry --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3bf1d8a..1c8ce4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ jobs: env: DEPS="nameko>=2.12.0" - python: 3.5 env: DEPS="nameko>=2.12.0" + - python: 3.6 stage: static install: pip install pre-commit script: make static From 74ce89fca5dae54370d7e84dc457a9954fa01adf Mon Sep 17 00:00:00 2001 From: Ondrej Kohout Date: Sat, 4 May 2019 07:54:15 +0200 Subject: [PATCH 4/5] Run pylint inside pre-commit --- .pre-commit-config.yaml | 8 ++++++++ Makefile | 5 +---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af309da..33f925f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,11 @@ repos: rev: 19.3b0 hooks: - id: black +- repo: local + hooks: + - id: pylint + name: pylint + entry: python -m pylint.__main__ --disable=R,C,W0221 + language: system + files: ^nameko_salesforce/ + types: [python] diff --git a/Makefile b/Makefile index 7aaf0a2..d76baae 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,8 @@ -test: static pylint pytest +test: static pytest static: pre-commit run --all-files -pylint: - pylint nameko_salesforce --disable=R,C,W0221 - pytest: coverage run --concurrency=eventlet --source nameko_salesforce --branch -m pytest tests coverage report --show-missing --fail-under=100 From 1847cc5c4ad8b2f8b5f5084845826a7226be444e Mon Sep 17 00:00:00 2001 From: Ondrej Kohout Date: Sat, 4 May 2019 07:59:05 +0200 Subject: [PATCH 5/5] Pylint needs complete installation --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1c8ce4e..9dbeeed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,9 @@ jobs: env: DEPS="nameko>=2.12.0" - python: 3.6 stage: static - install: pip install pre-commit + install: + - pip install -U -e ".[dev]" + - pip install pre-commit script: make static env: