From 7ad4e2d7721e13fa0cf69fd60fca4f3d3c7695a0 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Fri, 24 Jan 2025 09:51:12 -0500 Subject: [PATCH] chore: black-format --- docs/source/conf.py | 69 +- setup.py | 26 +- sync-page/event-handler.py | 33 +- sync2jira/__init__.py | 2 +- sync2jira/downstream_issue.py | 574 +++++--- sync2jira/downstream_pr.py | 41 +- sync2jira/intermediary.py | 180 ++- sync2jira/mailer.py | 10 +- sync2jira/main.py | 180 +-- sync2jira/upstream_issue.py | 247 ++-- sync2jira/upstream_pr.py | 32 +- tests/integration_tests/integration_test.py | 103 +- tests/integration_tests/jira_values.py | 12 +- tests/integration_tests/runtime_config.py | 66 +- tests/test_downstream_issue.py | 1445 +++++++++---------- tests/test_downstream_pr.py | 242 ++-- tests/test_intermediary.py | 257 ++-- tests/test_main.py | 413 +++--- tests/test_upstream_issue.py | 766 +++++----- tests/test_upstream_pr.py | 263 ++-- 20 files changed, 2572 insertions(+), 2389 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1d787aa1..4dcb3774 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,19 +15,19 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = u'Sync2Jira' -copyright = u'2019, Ralph Bean' -author = u'Ralph Bean' +project = "Sync2Jira" +copyright = "2019, Ralph Bean" +author = "Ralph Bean" # The short X.Y version -version = u'2.0' +version = "2.0" # The full version, including alpha/beta/rc tags -release = u'2.0' +release = "2.0" # -- General configuration --------------------------------------------------- @@ -40,28 +40,28 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['ntemplates'] +templates_path = ["ntemplates"] # 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" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -84,7 +84,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +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 @@ -95,7 +95,7 @@ # 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 = ['nstatic'] +html_static_path = ["nstatic"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -105,13 +105,13 @@ # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # -html_sidebars = { '**': ['globaltoc.html', 'searchbox.html'] } +html_sidebars = {"**": ["globaltoc.html", "searchbox.html"]} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'Sync2Jiradoc' +htmlhelp_basename = "Sync2Jiradoc" # -- Options for LaTeX output ------------------------------------------------ @@ -120,15 +120,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', @@ -138,8 +135,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Sync2Jira.tex', u'Sync2Jira Documentation', - u'Ralph Bean', 'manual'), + (master_doc, "Sync2Jira.tex", "Sync2Jira Documentation", "Ralph Bean", "manual"), ] @@ -147,10 +143,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'sync2jira', u'Sync2Jira Documentation', - [author], 1) -] +man_pages = [(master_doc, "sync2jira", "Sync2Jira Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -159,9 +152,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Sync2Jira', u'Sync2Jira Documentation', - author, 'Sync2Jira', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "Sync2Jira", + "Sync2Jira Documentation", + author, + "Sync2Jira", + "One line description of project.", + "Miscellaneous", + ), ] @@ -180,7 +179,7 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- diff --git a/setup.py b/setup.py index 882052ec..34ca47c8 100644 --- a/setup.py +++ b/setup.py @@ -20,22 +20,22 @@ from setuptools import setup -with open('requirements.txt', 'rb') as f: - install_requires = f.read().decode('utf-8').split('\n') - if not os.getenv('READTHEDOCS'): - install_requires.append('requests-kerberos') +with open("requirements.txt", "rb") as f: + install_requires = f.read().decode("utf-8").split("\n") + if not os.getenv("READTHEDOCS"): + install_requires.append("requests-kerberos") -with open('test-requirements.txt', 'rb') as f: - test_requires = f.read().decode('utf-8').split('\n') +with open("test-requirements.txt", "rb") as f: + test_requires = f.read().decode("utf-8").split("\n") setup( - name='sync2jira', + name="sync2jira", version=2.0, description="Sync GitHub issues to jira, via fedmsg", - author='Ralph Bean', - author_email='rbean@redhat.com', - url='https://github.com/release-engineering/Sync2Jira', - license='LGPLv2+', + author="Ralph Bean", + author_email="rbean@redhat.com", + url="https://github.com/release-engineering/Sync2Jira", + license="LGPLv2+", classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU Lesser General " @@ -47,12 +47,12 @@ install_requires=install_requires, tests_require=test_requires, packages=[ - 'sync2jira', + "sync2jira", ], include_package_data=True, zip_safe=False, entry_points={ - 'console_scripts': [ + "console_scripts": [ "sync2jira=sync2jira.main:main", "sync2jira-list-managed-urls=sync2jira.main:list_managed", "sync2jira-close-duplicates=sync2jira.main:close_duplicates", diff --git a/sync-page/event-handler.py b/sync-page/event-handler.py index af5ce1c1..2db2a8ee 100644 --- a/sync-page/event-handler.py +++ b/sync-page/event-handler.py @@ -10,8 +10,8 @@ # Global Variables app = Flask(__name__, static_url_path="/assets", static_folder="assets") -BASE_URL = os.environ['BASE_URL'] -REDIRECT_URL = os.environ['REDIRECT_URL'] +BASE_URL = os.environ["BASE_URL"] +REDIRECT_URL = os.environ["REDIRECT_URL"] config = load_config() # Set up our logging @@ -19,10 +19,10 @@ logging.basicConfig(format=FORMAT, level=logging.INFO) logging.basicConfig(format=FORMAT, level=logging.DEBUG) logging.basicConfig(format=FORMAT, level=logging.WARNING) -log = logging.getLogger('sync2jira-sync-page') +log = logging.getLogger("sync2jira-sync-page") -@app.route('/handle-event', methods=['POST']) +@app.route("/handle-event", methods=["POST"]) def handle_event(): """ Handler for when a user wants to sync a repo @@ -37,15 +37,16 @@ def handle_event(): initialize_pr(config, repo_name=repo_name) synced_repos.append(repo_name) if synced_repos: - return render_template('sync-page-success.jinja', - synced_repos=synced_repos, - url=f"https://{REDIRECT_URL}") + return render_template( + "sync-page-success.jinja", + synced_repos=synced_repos, + url=f"https://{REDIRECT_URL}", + ) else: - return render_template('sync-page-failure.jinja', - url=f"https://{REDIRECT_URL}") + return render_template("sync-page-failure.jinja", url=f"https://{REDIRECT_URL}") -@app.route('/', methods=['GET']) +@app.route("/", methods=["GET"]) def index(): """ Return relevant redirect @@ -53,16 +54,18 @@ def index(): return redirect("/github") -@app.route('/github', methods=['GET']) +@app.route("/github", methods=["GET"]) def github(): """ Github Sync Page """ # Build and return our updated HTML page - return render_template('sync-page-github.jinja', - github=config['sync2jira']['map']['github'], - url=f"https://{REDIRECT_URL}") + return render_template( + "sync-page-github.jinja", + github=config["sync2jira"]["map"]["github"], + url=f"https://{REDIRECT_URL}", + ) -if __name__ == '__main__': +if __name__ == "__main__": app.run(host=BASE_URL) diff --git a/sync2jira/__init__.py b/sync2jira/__init__.py index 042dd35b..4dfec3ec 100644 --- a/sync2jira/__init__.py +++ b/sync2jira/__init__.py @@ -16,4 +16,4 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110.15.0 USA # # Authors: Ralph Bean -__version__ = '1.7' +__version__ = "1.7" diff --git a/sync2jira/downstream_issue.py b/sync2jira/downstream_issue.py index 76d6ef6b..2d11e851 100644 --- a/sync2jira/downstream_issue.py +++ b/sync2jira/downstream_issue.py @@ -40,10 +40,10 @@ # This is used to ensure legacy comments are not touched UPDATE_DATE = datetime(2019, 7, 9, 18, 18, 36, 480291, tzinfo=timezone.utc) -log = logging.getLogger('sync2jira') +log = logging.getLogger("sync2jira") remote_link_title = "Upstream issue" -duplicate_issues_subject = 'FYI: Duplicate Sync2jira Issues' +duplicate_issues_subject = "FYI: Duplicate Sync2jira Issues" jira_cache = {} @@ -73,9 +73,13 @@ def _comment_format(comment): :returns: Comments formatted :rtype: String """ - pretty_date = comment['date_created'].strftime("%a %b %d") + pretty_date = comment["date_created"].strftime("%a %b %d") return "[%s] Upstream, %s wrote [%s]:\n\n{quote}\n%s\n{quote}" % ( - comment['id'], comment['author'], pretty_date, comment['body']) + comment["id"], + comment["author"], + pretty_date, + comment["body"], + ) def _comment_format_legacy(comment): @@ -89,7 +93,9 @@ def _comment_format_legacy(comment): :rtype: String """ return "Upstream, %s wrote:\n\n{quote}\n%s\n{quote}" % ( - comment['name'], comment['body']) + comment["name"], + comment["body"], + ) def get_jira_client(issue, config): @@ -113,14 +119,14 @@ def get_jira_client(issue, config): # Use the Jira instance set in the issue config. If none then # use the configured default jira instance. - jira_instance = issue.downstream.get('jira_instance', False) + jira_instance = issue.downstream.get("jira_instance", False) if not jira_instance: - jira_instance = config['sync2jira'].get('default_jira_instance', False) + jira_instance = config["sync2jira"].get("default_jira_instance", False) if not jira_instance: log.error("No jira_instance for issue and there is no default in the config") raise Exception - client = jira.client.JIRA(**config['sync2jira']['jira'][jira_instance]) + client = jira.client.JIRA(**config["sync2jira"]["jira"][jira_instance]) return client @@ -136,10 +142,12 @@ def _matching_jira_issue_query(client, issue, config, free=False): :rtype: List """ # Searches for any remote link to the issue.url - query = (f'issueFunction in linkedIssuesOfRemote("{remote_link_title}") and ' - f'issueFunction in linkedIssuesOfRemote("{issue.url}")') + query = ( + f'issueFunction in linkedIssuesOfRemote("{remote_link_title}") and ' + f'issueFunction in linkedIssuesOfRemote("{issue.url}")' + ) if free: - query += ' and statusCategory != Done' + query += " and statusCategory != Done" # Query the JIRA client and store the results results_of_query: jira.client.ResultList = client.search_issues(query) if len(results_of_query) > 1: @@ -150,8 +158,9 @@ def _matching_jira_issue_query(client, issue, config, free=False): description = result.fields.description or "" summary = result.fields.summary or "" if issue.id in description or issue.title == summary: - search = check_comments_for_duplicate(client, result, - find_username(issue, config)) + search = check_comments_for_duplicate( + client, result, find_username(issue, config) + ) if search is True: final_results.append(result) else: @@ -159,41 +168,50 @@ def _matching_jira_issue_query(client, issue, config, free=False): final_results.append(search) # If that's not the case, check if they have the same upstream title. # Upstream username/repo can change if repos are merged. - elif re.search(r"\[[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':\\|,.<>/?]*] " - + issue.upstream_title, - result.fields.summary): - search = check_comments_for_duplicate(client, result, - find_username(issue, config)) + elif re.search( + r"\[[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':\\|,.<>/?]*] " + + issue.upstream_title, + result.fields.summary, + ): + search = check_comments_for_duplicate( + client, result, find_username(issue, config) + ) if search is True: # We went through all the comments and didn't find anything # that indicated it was a duplicate - log.warning('Matching downstream issue %s to upstream issue %s' % - (result.fields.summary, issue.title)) + log.warning( + "Matching downstream issue %s to upstream issue %s" + % (result.fields.summary, issue.title) + ) final_results.append(result) else: # Else search returned a linked issue final_results.append(search) if not final_results: # Just return the most updated issue - results_of_query.sort(key=lambda x: datetime.strptime( - x.fields.updated, '%Y-%m-%dT%H:%M:%S.%f+0000')) + results_of_query.sort( + key=lambda x: datetime.strptime( + x.fields.updated, "%Y-%m-%dT%H:%M:%S.%f+0000" + ) + ) final_results.append(results_of_query[0]) # Return the final_results log.debug("Found %i results for query %r", len(final_results), query) # Alert the owner - if issue.downstream.get('owner'): - alert_user_of_duplicate_issues(issue, final_results, - results_of_query, - config, client) + if issue.downstream.get("owner"): + alert_user_of_duplicate_issues( + issue, final_results, results_of_query, config, client + ) return final_results else: return results_of_query -def alert_user_of_duplicate_issues(issue, final_result, results_of_query, - config, client): +def alert_user_of_duplicate_issues( + issue, final_result, results_of_query, config, client +): """ Alerts owner of duplicate downstream issues. @@ -210,34 +228,36 @@ def alert_user_of_duplicate_issues(issue, final_result, results_of_query, # Check that all duplicate issues are closed updated_results = [] for result in results_of_query: - if result.fields.status.name != 'Closed': + if result.fields.status.name != "Closed": updated_results.append(result) if not updated_results: # Nothing to alert the owner of return # Get base URL - jira_instance = issue.downstream.get('jira_instance', False) + jira_instance = issue.downstream.get("jira_instance", False) if not jira_instance: - jira_instance = config['sync2jira'].get('default_jira_instance', False) + jira_instance = config["sync2jira"].get("default_jira_instance", False) if not jira_instance: log.error("No jira_instance for issue and there is no default in the config") raise Exception - base_url = config['sync2jira']['jira'][jira_instance]['options']['server'] + '/browse/' + base_url = ( + config["sync2jira"]["jira"][jira_instance]["options"]["server"] + "/browse/" + ) # Format the updated results template_ready = [] for update in updated_results: url = base_url + update.key - new_entry = {'url': url, 'title': update.key} + new_entry = {"url": url, "title": update.key} template_ready.append(new_entry) # Get owner name and email from Jira - ds_owner = issue.downstream.get('owner') + ds_owner = issue.downstream.get("owner") ds_owner = ds_owner.strip() if ds_owner else ds_owner ret = client.search_users(ds_owner) if len(ret) > 1: - log.warning('Found multiple users for username %s', ds_owner) + log.warning("Found multiple users for username %s", ds_owner) found = False for person in ret: if person.key == ds_owner: @@ -245,25 +265,27 @@ def alert_user_of_duplicate_issues(issue, final_result, results_of_query, found = True break if not found: - log.warning('Could not find JIRA user for username %s', ds_owner) + log.warning("Could not find JIRA user for username %s", ds_owner) if not ret: - log.warning('No owner could be found for username %s', ds_owner) + log.warning("No owner could be found for username %s", ds_owner) return - user = {'name': ret[0].displayName, 'email': ret[0].emailAddress} + user = {"name": ret[0].displayName, "email": ret[0].emailAddress} # Format selected issue - selected_issue = {'url': base_url + final_result[0].key, - 'title': final_result[0].key} + selected_issue = { + "url": base_url + final_result[0].key, + "title": final_result[0].key, + } # Get admin information admins = [] admin_template = [] - for admin in config['sync2jira']['admins']: + for admin in config["sync2jira"]["admins"]: admin_username = next(name for name in admin).strip() ret = client.search_users(admin_username) if len(ret) > 1: - log.warning('Found multiple users for admin %s', admin_username) + log.warning("Found multiple users for admin %s", admin_username) found = False for person in ret: if person.key == ds_owner: @@ -271,32 +293,40 @@ def alert_user_of_duplicate_issues(issue, final_result, results_of_query, found = True break if not found: - log.warning('Could not find JIRA user for admin %s', admin_username) + log.warning("Could not find JIRA user for admin %s", admin_username) if not ret: - message = f'No admin could be found for username {admin_username}' + message = f"No admin could be found for username {admin_username}" log.warning(message) raise ValueError(message) admins.append(ret[0].emailAddress) - admin_template.append({'name': ret[0].displayName, 'email': ret[0].emailAddress}) + admin_template.append( + {"name": ret[0].displayName, "email": ret[0].emailAddress} + ) # Create and send email template_loader = jinja2.FileSystemLoader( - searchpath='usr/local/src/sync2jira/sync2jira/') + searchpath="usr/local/src/sync2jira/sync2jira/" + ) template_env = jinja2.Environment(loader=template_loader, autoescape=True) - template = template_env.get_template('email_template.jinja') - html_text = template.render(user=user, - admins=admin_template, - issue=issue, - selected_issue=selected_issue, - duplicate_issues=template_ready) + template = template_env.get_template("email_template.jinja") + html_text = template.render( + user=user, + admins=admin_template, + issue=issue, + selected_issue=selected_issue, + duplicate_issues=template_ready, + ) # Send mail - send_mail(recipients=[user['email']], - cc=admins, - subject=duplicate_issues_subject, - text=html_text) - log.info('Alerted %s about %s duplicate issue(s)' % - (user['email'], len(template_ready))) + send_mail( + recipients=[user["email"]], + cc=admins, + subject=duplicate_issues_subject, + text=html_text, + ) + log.info( + "Alerted %s about %s duplicate issue(s)" % (user["email"], len(template_ready)) + ) def find_username(issue, config): @@ -308,13 +338,13 @@ def find_username(issue, config): :returns: Username string :rtype: String """ - jira_instance = issue.downstream.get('jira_instance', False) + jira_instance = issue.downstream.get("jira_instance", False) if not jira_instance: - jira_instance = config['sync2jira'].get('default_jira_instance', False) + jira_instance = config["sync2jira"].get("default_jira_instance", False) if not jira_instance: log.error("No jira_instance for issue and there is no default in the config") raise Exception - return config['sync2jira']['jira_username'] + return config["sync2jira"]["jira_username"] def check_comments_for_duplicate(client, result, username): @@ -330,10 +360,9 @@ def check_comments_for_duplicate(client, result, username): :rtype: Bool or jira.resource.Issue """ for comment in client.comments(result): - search = re.search(r'Marking as duplicate of (\w*)-(\d*)', - comment.body) + search = re.search(r"Marking as duplicate of (\w*)-(\d*)", comment.body) if search and comment.author.name == username: - issue_id = search.groups()[0] + '-' + search.groups()[1] + issue_id = search.groups()[0] + "-" + search.groups()[1] return client.issue(issue_id) return True @@ -350,23 +379,23 @@ def _find_comment_in_jira(comment, j_comments): formatted_comment = _comment_format(comment) legacy_formatted_comment = _comment_format_legacy(comment) for item in j_comments: - if item.raw['body'] == legacy_formatted_comment: + if item.raw["body"] == legacy_formatted_comment: # If the comment is in the legacy comment format # return the item return item - if str(comment['id']) in item.raw['body']: + if str(comment["id"]) in item.raw["body"]: # The comment id's match, if they don't have the same body, # we need to edit the comment - if item.raw['body'] != formatted_comment: + if item.raw["body"] != formatted_comment: # We need to update the comment item.update(body=formatted_comment) - log.info('Updated one comment') + log.info("Updated one comment") # Now we can just return the item return item else: # Else they are equal and we can return the item return item - if comment['date_created'] < UPDATE_DATE: + if comment["date_created"] < UPDATE_DATE: # If the comments date is prior to the update_date # We should not try to touch the comment return item @@ -384,10 +413,11 @@ def _comment_matching(g_comments, j_comments): """ return list( filter( - lambda x: _find_comment_in_jira(x, j_comments) is None or x['changed'] is not None, - g_comments - ) + lambda x: _find_comment_in_jira(x, j_comments) is None + or x["changed"] is not None, + g_comments, ) + ) def _get_existing_jira_issue(client, issue, config): @@ -418,9 +448,10 @@ def _get_existing_jira_issue_legacy(client, issue): kwargs["External issue URL"] = str(issue.url) kwargs = sorted(kwargs.items(), key=operator.itemgetter(0)) - query = " AND ".join( - f"'{k}'='{v}'" for k, v in kwargs if v is not None - ) + " AND (resolution is null OR resolution = Duplicate)" + query = ( + " AND ".join(f"'{k}'='{v}'" for k, v in kwargs if v is not None) + + " AND (resolution is null OR resolution = Duplicate)" + ) results = client.search_issues(query) if results: return results[0] @@ -453,7 +484,7 @@ def attach_link(client, downstream, remote_link): # gets re-indexed, otherwise our searches won't work. Also, Handle some # weird API changes here... log.debug("Modifying desc of %r to trigger re-index.", downstream.key) - downstream.update({'description': modified_desc}) + downstream.update({"description": modified_desc}) return downstream @@ -465,7 +496,7 @@ def _upgrade_jira_issue(client, downstream, issue, config): Simply mark it with an external-url field value. """ log.info("Upgrading %r %r issue for %r", downstream.key, issue.downstream, issue) - if config['sync2jira']['testing']: + if config["sync2jira"]["testing"]: log.info("Testing flag is true. Skipping actual upgrade.") return @@ -488,7 +519,7 @@ def assign_user(client, issue, downstream, remove_all=False): # If removeAll flag, then we need to reset the assignees if remove_all: # Update the issue to have no assignees - downstream.update(assignee={'name': ''}) + downstream.update(assignee={"name": ""}) # Then we're done! And we can go back ! return @@ -501,8 +532,8 @@ def assign_user(client, issue, downstream, remove_all=False): # See if any of the upstream users has full names available. Not all do. def assignee_fullname(u_issue): for assignee in u_issue.assignee: - if assignee['fullname']: - return assignee['fullname'] + if assignee["fullname"]: + return assignee["fullname"] return None fullname = assignee_fullname(issue) @@ -512,22 +543,22 @@ def assignee_fullname(u_issue): # Make API call to get a list of users users = client.search_assignable_users_for_issues( - fullname, - project=issue.downstream['project']) + fullname, project=issue.downstream["project"] + ) # Loop through the query for user in users: - if user.displayName == issue.assignee[0]['fullname']: + if user.displayName == issue.assignee[0]["fullname"]: # Then we can assign the issue to the user - downstream.update({'assignee': {'name': user.name}}) + downstream.update({"assignee": {"name": user.name}}) return # If there is an owner, assign it to them - owner = issue.downstream.get('owner') + owner = issue.downstream.get("owner") if owner: client.assign_issue(downstream.id, owner) - log.warning('Assigned %s to owner: %s', issue.title, owner) + log.warning("Assigned %s to owner: %s", issue.title, owner) return - log.warning('Was not able to assign user %s', issue.assignee[0]['fullname']) + log.warning("Was not able to assign user %s", issue.assignee[0]["fullname"]) def change_status(client, downstream, status, issue): @@ -541,21 +572,23 @@ def change_status(client, downstream, status, issue): :param sync2jira.intermediary.Issue issue: Issue object """ transitions = client.transitions(downstream) - tid = '' + tid = "" for t in transitions: - if t['name'] and status.upper() == str(t['name']).upper(): - tid = int(t['id']) + if t["name"] and status.upper() == str(t["name"]).upper(): + tid = int(t["id"]) break if tid: try: client.transition_issue(downstream, tid) - log.info('Updated downstream to %s status for issue %s', - status, issue.title) + log.info( + "Updated downstream to %s status for issue %s", status, issue.title + ) except JIRAError: - log.error('Updating downstream issue failed for %s: %s', - status, issue.title) + log.error( + "Updating downstream issue failed for %s: %s", status, issue.title + ) else: - log.warning('Could not update JIRA %s for %s', status, issue.title) + log.warning("Could not update JIRA %s for %s", status, issue.title) def _get_preferred_issue_types(config, issue): @@ -578,12 +611,12 @@ def _get_preferred_issue_types(config, issue): # } type_list = [] - cmap = config['sync2jira'].get('map', {}) - conf = cmap.get('github', {}).get(issue.upstream, {}) + cmap = config["sync2jira"].get("map", {}) + conf = cmap.get("github", {}).get(issue.upstream, {}) # we consider the issue_types mapping if it exists. If it does, exclude all other logic. - if 'issue_types' in conf: - for tag, issue_type in conf['issue_types'].items(): + if "issue_types" in conf: + for tag, issue_type in conf["issue_types"].items(): if tag in issue.tags: type_list.insert(0, issue_type) type_list.sort() @@ -591,14 +624,14 @@ def _get_preferred_issue_types(config, issue): # if issue_types was not provided, we consider the type option next. If that is not set # fall back to the old behavior. if not type_list: - if 'type' in conf: - type_list.insert(0, conf['type']) + if "type" in conf: + type_list.insert(0, conf["type"]) else: if "RFE" in issue.title: - type_list.insert(0, 'Story') + type_list.insert(0, "Story") else: - type_list.insert(0, 'Bug') - log.debug('Preferred issue type list: %s' % type_list) + type_list.insert(0, "Bug") + log.debug("Preferred issue type list: %s" % type_list) return type_list @@ -613,7 +646,7 @@ def _create_jira_issue(client, issue, config): :returns: Returns JIRA issue that was created :rtype: jira.resources.Issue """ - custom_fields = issue.downstream.get('custom_fields', {}) + custom_fields = issue.downstream.get("custom_fields", {}) preferred_types = _get_preferred_issue_types(config, issue) description = _build_description(issue) @@ -622,11 +655,11 @@ def _create_jira_issue(client, issue, config): description=description, issuetype=dict(name=preferred_types[0]), ) - if issue.downstream['project']: - kwargs['project'] = dict(key=issue.downstream['project']) - if issue.downstream.get('component'): + if issue.downstream["project"]: + kwargs["project"] = dict(key=issue.downstream["project"]) + if issue.downstream.get("component"): # TODO - make this a list in the config - kwargs['components'] = [dict(name=issue.downstream['component'])] + kwargs["components"] = [dict(name=issue.downstream["component"])] for key, custom_field in custom_fields.items(): if type(custom_field) is str: @@ -635,67 +668,81 @@ def _create_jira_issue(client, issue, config): kwargs[key] = custom_field # Add labels if needed - if 'labels' in issue.downstream.keys(): - kwargs['labels'] = issue.downstream['labels'] + if "labels" in issue.downstream.keys(): + kwargs["labels"] = issue.downstream["labels"] log.info("Creating issue for %r: %r", issue, kwargs) - if config['sync2jira']['testing']: + if config["sync2jira"]["testing"]: log.info("Testing flag is true. Skipping actual creation.") return downstream = client.create_issue(**kwargs) # Add Epic link, QA, EXD-Service field if present - if issue.downstream.get('epic-link') or \ - issue.downstream.get('qa-contact') or \ - issue.downstream.get('EXD-Service'): + if ( + issue.downstream.get("epic-link") + or issue.downstream.get("qa-contact") + or issue.downstream.get("EXD-Service") + ): # Fetch all fields all_fields = client.fields() # Make a map from field name -> field id - name_map = {field['name']: field['id'] for field in all_fields} - if issue.downstream.get('epic-link'): + name_map = {field["name"]: field["id"] for field in all_fields} + if issue.downstream.get("epic-link"): # Try to get and update the custom field - custom_field: Optional[str] = name_map.get('Epic Link') + custom_field: Optional[str] = name_map.get("Epic Link") if custom_field: try: - downstream.update({custom_field: issue.downstream['epic-link']}) + downstream.update({custom_field: issue.downstream["epic-link"]}) except JIRAError: - client.add_comment(downstream, - f"Error adding Epic-Link: {issue.downstream['epic-link']}") - if issue.downstream.get('qa-contact'): + client.add_comment( + downstream, + f"Error adding Epic-Link: {issue.downstream['epic-link']}", + ) + if issue.downstream.get("qa-contact"): # Try to get and update the custom field - custom_field = name_map.get('QA Contact') + custom_field = name_map.get("QA Contact") if custom_field: - downstream.update({custom_field: issue.downstream['qa-contact']}) - if issue.downstream.get('EXD-Service'): + downstream.update({custom_field: issue.downstream["qa-contact"]}) + if issue.downstream.get("EXD-Service"): # Try to update the custom field - exd_service_info = issue.downstream['EXD-Service'] - custom_field = name_map.get('EXD-Service') + exd_service_info = issue.downstream["EXD-Service"] + custom_field = name_map.get("EXD-Service") if custom_field: try: downstream.update( - {custom_field: {"value": f"{exd_service_info['guild']}", - "child": {"value": f"{exd_service_info['value']}"}}}) + { + custom_field: { + "value": f"{exd_service_info['guild']}", + "child": {"value": f"{exd_service_info['value']}"}, + } + } + ) except JIRAError: - client.add_comment(downstream, - f"Error adding EXD-Service field.\n" - f"Project: {exd_service_info['guild']}\n" - f"Value: {exd_service_info['value']}") + client.add_comment( + downstream, + f"Error adding EXD-Service field.\n" + f"Project: {exd_service_info['guild']}\n" + f"Value: {exd_service_info['value']}", + ) # Add upstream issue ID in comment if required - if 'upstream_id' in issue.downstream.get('issue_updates', []): - comment = f"Creating issue for " \ + if "upstream_id" in issue.downstream.get("issue_updates", []): + comment = ( + f"Creating issue for " f"[{issue.upstream}-#{issue.upstream_id}|{issue.url}]" + ) client.add_comment(downstream, comment) if len(preferred_types) > 1: - comment = ('Some labels look like issue types but were not considered:' - + str({preferred_types[1:]})) + comment = "Some labels look like issue types but were not considered:" + str( + {preferred_types[1:]} + ) client.add_comment(downstream, comment) remote_link = dict(url=issue.url, title=remote_link_title) attach_link(client, downstream, remote_link) - default_status = issue.downstream.get('default_status') + default_status = issue.downstream.get("default_status") if default_status is not None: change_status(client, downstream, default_status, issue) @@ -737,7 +784,7 @@ def _update_jira_issue(existing, issue, client, config): log.info("Updating information for upstream issue: %s", issue.title) # Get a list of what the user wants to update for the upstream issue - updates = issue.downstream.get('issue_updates', []) + updates = issue.downstream.get("issue_updates", []) # Update relevant data if needed. # If the user has specified nothing, just return. @@ -745,47 +792,48 @@ def _update_jira_issue(existing, issue, client, config): return # Get fields representing project item fields in GitHub and Jira - github_project_fields = issue.downstream.get('github_project_fields', {}) + github_project_fields = issue.downstream.get("github_project_fields", {}) # Only synchronize comments for listings that op-in - if 'github_project_fields' in updates and len(github_project_fields) > 0: + if "github_project_fields" in updates and len(github_project_fields) > 0: log.info("Looking for GitHub project fields") - _update_github_project_fields(client, existing, issue, - github_project_fields, config) + _update_github_project_fields( + client, existing, issue, github_project_fields, config + ) # Only synchronize comments for listings that op-in - if 'comments' in updates: + if "comments" in updates: log.info("Looking for new comments") _update_comments(client, existing, issue) # Only synchronize tags for listings that op-in - if any('tags' in item for item in updates): + if any("tags" in item for item in updates): log.info("Looking for new tags") _update_tags(updates, existing, issue) # Only synchronize fixVersion for listings that op-in - if any('fixVersion' in item for item in updates) and issue.fixVersion: + if any("fixVersion" in item for item in updates) and issue.fixVersion: log.info("Looking for new fixVersions") _update_fixVersion(updates, existing, issue, client) # Only synchronize assignee for listings that op-in - if any('assignee' in item for item in updates): + if any("assignee" in item for item in updates): log.info("Looking for new assignee(s)") _update_assignee(client, existing, issue, updates) # Only synchronize descriptions for listings that op-in - if 'description' in updates: + if "description" in updates: log.info("Looking for new description") _update_description(existing, issue) # Only synchronize title for listings that op-in - if 'title' in updates: + if "title" in updates: # Update the title if needed if issue.title != existing.fields.summary: log.info("Looking for new title") _update_title(issue, existing) # Only synchronize transition (status) for listings that op-in - if any('transition' in item for item in updates): + if any("transition" in item for item in updates): log.info("Looking for new transition(s)") _update_transition(client, existing, issue) @@ -793,7 +841,7 @@ def _update_jira_issue(existing, issue, client, config): log.info("Attempting to update downstream issue on upstream closed event") _update_on_close(existing, issue, updates) - log.info('Done updating %s!', issue.title) + log.info("Done updating %s!", issue.title) def _update_transition(client, existing, issue): @@ -809,9 +857,14 @@ def _update_transition(client, existing, issue): # downstream JIRA ticket # First get the closed status from the config file - closed_status = next(filter(lambda d: "transition" in d, issue.downstream.get('issue_updates', {})))['transition'] - if closed_status is not True and issue.status == 'Closed' \ - and existing.fields.status.name.upper() != closed_status.upper(): + closed_status = next( + filter(lambda d: "transition" in d, issue.downstream.get("issue_updates", {})) + )["transition"] + if ( + closed_status is not True + and issue.status == "Closed" + and existing.fields.status.name.upper() != closed_status.upper() + ): # Now we need to update the status of the JIRA issue # First add a comment indicating the change (in case it doesn't go through) hyperlink = f"[Upstream issue|{issue.url}]" @@ -831,9 +884,9 @@ def _update_title(issue, existing): :returns: Nothing """ # Then we can update the title - data = {'summary': issue.title} + data = {"summary": issue.title} existing.update(data) - log.info('Updated title') + log.info("Updated title") def _update_comments(client, existing, issue): @@ -870,11 +923,13 @@ def _update_fixVersion(updates, existing, issue, client): """ fix_version = [] # If we are not supposed to overwrite JIRA content - if not bool(next(filter(lambda d: "fixVersion" in d, updates))['fixVersion']['overwrite']): + if not bool( + next(filter(lambda d: "fixVersion" in d, updates))["fixVersion"]["overwrite"] + ): # We need to make sure we're not deleting any fixVersions on JIRA # Get all fixVersions for the issue for version in existing.fields.fixVersions: - fix_version.append({'name': version.name}) + fix_version.append({"name": version.name}) # GitHub does not allow for multiple fixVersions (milestones) # But JIRA does, that is why we're looping here. Hopefully one @@ -882,48 +937,57 @@ def _update_fixVersion(updates, existing, issue, client): for version in issue.fixVersion: if version is not None: # Update the fixVersion only if it's already not in JIRA - result = filter(lambda v: v['name'] == str(version), fix_version) + result = filter(lambda v: v["name"] == str(version), fix_version) # If we have a result skip, if not then add it to fix_version if not result or not list(result): - fix_version.append({'name': version}) + fix_version.append({"name": version}) # We don't want to make an API call if the labels are the same jira_labels = [] for label in existing.fields.fixVersions: - jira_labels.append({'name': label.name}) - res = [i for i in jira_labels if i not in fix_version] + \ - [j for j in fix_version if j not in jira_labels] + jira_labels.append({"name": label.name}) + res = [i for i in jira_labels if i not in fix_version] + [ + j for j in fix_version if j not in jira_labels + ] if res: - data = {'fixVersions': fix_version} + data = {"fixVersions": fix_version} # If the fixVersion is not in JIRA, it will throw an error try: existing.update(data) - log.info('Updated %s fixVersion(s)', len(fix_version)) + log.info("Updated %s fixVersion(s)", len(fix_version)) except JIRAError: - log.warning('Error updating the fixVersion. %s is an invalid fixVersion.', - issue.fixVersion) + log.warning( + "Error updating the fixVersion. %s is an invalid fixVersion.", + issue.fixVersion, + ) # Add a comment to indicate there was an issue - client.add_comment(existing, f"Error updating fixVersion: {issue.fixVersion}") + client.add_comment( + existing, f"Error updating fixVersion: {issue.fixVersion}" + ) def _update_assignee(client, existing, issue, updates): """ - Helper function update existing JIRA assignee from downstream issue. + Helper function update existing JIRA assignee from downstream issue. - :param jira.client.JIRA client: JIRA client - :param jira.resource.Issue existing: Existing JIRA issue - :param sync2jira.intermediary.Issue issue: Upstream issue - :param List updates: Downstream updates requested by the user - :returns: Nothing + :param jira.client.JIRA client: JIRA client + :param jira.resource.Issue existing: Existing JIRA issue + :param sync2jira.intermediary.Issue issue: Upstream issue + :param List updates: Downstream updates requested by the user + :returns: Nothing """ # First check if overwrite is set to True - overwrite = bool(next(filter(lambda d: "assignee" in d, updates))['assignee']['overwrite']) + overwrite = bool( + next(filter(lambda d: "assignee" in d, updates))["assignee"]["overwrite"] + ) # First check if the issue is already assigned to the same person update = False if issue.assignee and issue.assignee[0]: try: - update = issue.assignee[0]['fullname'] != existing.fields.assignee.displayName + update = ( + issue.assignee[0]["fullname"] != existing.fields.assignee.displayName + ) except AttributeError: update = True @@ -934,19 +998,19 @@ def _update_assignee(client, existing, issue, updates): if issue.assignee[0] and update: # Update the assignee assign_user(client, issue, existing) - log.info('Updated assignee') + log.info("Updated assignee") return else: # Update the assignee if we have someone to assignee it too if update: assign_user(client, issue, existing) - log.info('Updated assignee') + log.info("Updated assignee") else: if existing.fields.assignee and not issue.assignee: # Else we should remove all assignees # Set removeAll flag to true assign_user(client, issue, existing, remove_all=True) - log.info('Updated assignee') + log.info("Updated assignee") def _update_jira_labels(issue, labels): @@ -962,13 +1026,14 @@ def _update_jira_labels(issue, labels): if _labels == sorted(issue.fields.labels): return - data = {'labels': _labels} + data = {"labels": _labels} issue.update(data) - log.info('Updated %s tag(s)', len(_labels)) + log.info("Updated %s tag(s)", len(_labels)) -def _update_github_project_fields(client, existing, issue, - github_project_fields, config): +def _update_github_project_fields( + client, existing, issue, github_project_fields, config +): """Update a Jira issue with GitHub project item field values :param jira.client.JIRA client: JIRA client @@ -978,57 +1043,77 @@ def _update_github_project_fields(client, existing, issue, :param dict config: configuration options """ - default_jira_fields = config['sync2jira'].get('default_jira_fields', {}) + default_jira_fields = config["sync2jira"].get("default_jira_fields", {}) for name, values in github_project_fields.items(): if name not in dir(issue): - log.error(f"Configuration error: github_project_field key, {name:r}, is not in issue object.") + log.error( + f"Configuration error: github_project_field key, {name:r}, is not in issue object." + ) continue log.info(f"Looking at GHP field '{name}' with configuration '{values}'") fieldvalue = getattr(issue, name) log.info(f"Issue value for field '{name}' is '{fieldvalue}'") - if name == 'storypoints': + if name == "storypoints": if not isinstance(fieldvalue, int): if fieldvalue is not None: - log.info(f"Story point field value '{fieldvalue}' is a {type(fieldvalue)}, not an 'int'") + log.info( + f"Story point field value '{fieldvalue}' is a {type(fieldvalue)}, not an 'int'" + ) continue try: - jirafieldname = default_jira_fields['storypoints'] + jirafieldname = default_jira_fields["storypoints"] log.info(f"Jira issue story point field name is: '{jirafieldname}'") except KeyError: - log.error("Configuration error: Missing 'storypoints' in `default_jira_fields`") + log.error( + "Configuration error: Missing 'storypoints' in `default_jira_fields`" + ) continue try: existing.update({jirafieldname: fieldvalue}) log.info("Jira issue story point update was successful") except JIRAError as err: # Note the failure in a comment to the downstream issue - log.error(f"Error updating Jira issue story points field ({jirafieldname}: {fieldvalue}): {err}") + log.error( + f"Error updating Jira issue story points field ({jirafieldname}: {fieldvalue}): {err}" + ) client.add_comment( existing, "Error updating GitHub project storypoints field ({}: {}): {}".format( - jirafieldname, fieldvalue, err)) - elif name == 'priority': - jira_priority = values.get('options', {}).get(fieldvalue) + jirafieldname, fieldvalue, err + ), + ) + elif name == "priority": + jira_priority = values.get("options", {}).get(fieldvalue) if not jira_priority: - log.info(f"Priority field value mapping for '{fieldvalue}' is '{jira_priority}'") + log.info( + f"Priority field value mapping for '{fieldvalue}' is '{jira_priority}'" + ) continue try: - jirafieldname = default_jira_fields['priority'] - log.info(f"Configured Jira issue priority field name is: '{jirafieldname}'") + jirafieldname = default_jira_fields["priority"] + log.info( + f"Configured Jira issue priority field name is: '{jirafieldname}'" + ) except KeyError: - jirafieldname = 'priority' - log.info(f"Default Jira issue priority field name is: '{jirafieldname}'") + jirafieldname = "priority" + log.info( + f"Default Jira issue priority field name is: '{jirafieldname}'" + ) try: - existing.update({jirafieldname: {'name': jira_priority}}) + existing.update({jirafieldname: {"name": jira_priority}}) log.info("Jira issue priority update was successful") except JIRAError as err: # Note the failure in a comment to the downstream issue - log.error(f"Error updating Jira issue priority field ({jirafieldname}: {jira_priority}): {err}") + log.error( + f"Error updating Jira issue priority field ({jirafieldname}: {jira_priority}): {err}" + ) client.add_comment( existing, "Error updating GitHub project priority field ({}: {}): {}".format( - jirafieldname, jira_priority, err)) + jirafieldname, jira_priority, err + ), + ) def _update_tags(updates, existing, issue): @@ -1044,7 +1129,7 @@ def _update_tags(updates, existing, issue): updated_labels = issue.tags # Ensure no duplicates if overwrite is set to false - if not bool(next(filter(lambda d: "tags" in d, updates))['tags']['overwrite']): + if not bool(next(filter(lambda d: "tags" in d, updates))["tags"]["overwrite"]): updated_labels = _label_matching(updated_labels, existing.fields.labels) # Ensure that the tags are all valid @@ -1056,26 +1141,26 @@ def _update_tags(updates, existing, issue): def _build_description(issue): # Build the description of the JIRA issue - if 'description' in issue.downstream.get('issue_updates', {}): + if "description" in issue.downstream.get("issue_updates", {}): description = "Upstream description: {quote}%s{quote}" % issue.content else: - description = '' + description = "" - if any('transition' in item for item in issue.downstream.get('issue_updates', {})): + if any("transition" in item for item in issue.downstream.get("issue_updates", {})): # Just add it to the top of the description formatted_status = "Upstream issue status: " + issue.status - description = formatted_status + '\n' + description + description = formatted_status + "\n" + description if issue.reporter: # Add to the description - description = '[%s] Upstream Reporter: %s\n%s' % ( + description = "[%s] Upstream Reporter: %s\n%s" % ( issue.id, - issue.reporter['fullname'], - description + issue.reporter["fullname"], + description, ) # Add the url if requested - if 'url' in issue.downstream.get('issue_updates', {}): + if "url" in issue.downstream.get("issue_updates", {}): description = description + f"\nUpstream URL: {issue.url}" return description @@ -1103,9 +1188,9 @@ def _update_description(existing, issue): log.info(f"DEBUG: Old: {existing.fields.description}") log.info(f"DEBUG: New: {new_description}") - data = {'description': new_description} + data = {"description": new_description} existing.update(data) - log.info('Updated description') + log.info("Updated description") def _update_on_close(existing, issue, updates): @@ -1133,21 +1218,21 @@ def _update_on_close(existing, issue, updates): """ on_close_updates = None for item in updates: - if 'on_close' in item: - on_close_updates = item['on_close'] + if "on_close" in item: + on_close_updates = item["on_close"] break if not on_close_updates: return - if issue.status != 'Closed': + if issue.status != "Closed": return - if 'apply_labels' not in on_close_updates: + if "apply_labels" not in on_close_updates: return updated_labels = list( - set(existing.fields.labels).union(set(on_close_updates['apply_labels'])) + set(existing.fields.labels).union(set(on_close_updates["apply_labels"])) ) log.info("Applying 'on_close' labels to downstream Jira issue") _update_jira_labels(existing, updated_labels) @@ -1183,14 +1268,17 @@ def sync_with_jira(issue, config): client = get_jira_client(issue, config) # Check the status of the JIRA client - if not config['sync2jira']['develop'] and not check_jira_status(client): - log.warning('The JIRA server looks like its down. Shutting down...') + if not config["sync2jira"]["develop"] and not check_jira_status(client): + log.warning("The JIRA server looks like its down. Shutting down...") raise JIRAError - if issue.downstream.get('issue_updates'): - if issue.source == 'github' and issue.content and \ - 'github_markdown' in issue.downstream['issue_updates']: - issue.content = pypandoc.convert_text(issue.content, 'jira', format='gfm') + if issue.downstream.get("issue_updates"): + if ( + issue.source == "github" + and issue.content + and "github_markdown" in issue.downstream["issue_updates"] + ): + issue.content = pypandoc.convert_text(issue.content, "jira", format="gfm") # First, check to see if we have a matching issue using the new method. # If we do, then just bail out. No sync needed. @@ -1199,7 +1287,7 @@ def sync_with_jira(issue, config): if existing: # If we found an existing JIRA issue already log.info("Found existing, matching downstream %r.", existing.key) - if config['sync2jira']['testing']: + if config["sync2jira"]["testing"]: log.info("Testing flag is true. Skipping actual update.") return # Update relevant metadata (i.e. tags, assignee, etc) @@ -1208,7 +1296,7 @@ def sync_with_jira(issue, config): # If we're *not* configured to do legacy matching (upgrade mode) then there # is nothing left to do than to but to create the issue and return. - if not config['sync2jira'].get('legacy_matching', True): + if not config["sync2jira"].get("legacy_matching", True): log.debug("Legacy matching disabled.") _create_jira_issue(client, issue, config) return @@ -1236,26 +1324,32 @@ def _close_as_duplicate(client: jira.client, duplicate, keeper, config): :returns: Nothing """ log.info("Closing %s as duplicate of %s", duplicate.permalink(), keeper.permalink()) - if config['sync2jira']['testing']: + if config["sync2jira"]["testing"]: log.info("Testing flag is true. Skipping actual delete.") return # Find the id of some dropped or done state. - transitions = {t['name']: t['id'] for t in client.transitions(duplicate)} + transitions = {t["name"]: t["id"] for t in client.transitions(duplicate)} closed = None - preferences = ['Dropped', 'Reject', 'Done', 'Closed', 'Closed (2)', ] + preferences = [ + "Dropped", + "Reject", + "Done", + "Closed", + "Closed (2)", + ] for preference in preferences: if preference in transitions: closed = transitions[preference] break - text = 'Marking as duplicate of %s' % keeper.key + text = "Marking as duplicate of %s" % keeper.key if any(text in comment.body for comment in client.comments(duplicate)): log.info("Skipping comment in duplicate. Already present.") else: client.add_comment(duplicate, text) - text = '%s is a duplicate.' % duplicate.key + text = "%s is a duplicate." % duplicate.key if any(text in comment.body for comment in client.comments(keeper)): log.info("Skipping comment original. Already present.") else: @@ -1263,21 +1357,25 @@ def _close_as_duplicate(client: jira.client, duplicate, keeper, config): if closed: try: - client.transition_issue(duplicate, closed, resolution={'name': 'Duplicate'}) + client.transition_issue(duplicate, closed, resolution={"name": "Duplicate"}) except Exception as e: - if ('response' in dir(e) and 'text' in dir(e.response) - and "Field 'resolution' cannot be set" in e.response.text): + if ( + "response" in dir(e) + and "text" in dir(e.response) + and "Field 'resolution' cannot be set" in e.response.text + ): # Try closing without a specific resolution. try: client.transition_issue(duplicate, closed) except Exception: log.exception( - "Failed to close %r without a resolution", - duplicate.permalink()) + "Failed to close %r without a resolution", duplicate.permalink() + ) else: log.exception( "Failed to close %r with a resolution of 'Duplicate'", - duplicate.permalink()) + duplicate.permalink(), + ) else: log.warning("Unable to find close transition for %r", duplicate.key) @@ -1294,8 +1392,8 @@ def close_duplicates(issue, config): client = get_jira_client(issue, config) # Check the status of the JIRA client - if not config['sync2jira']['develop'] and not check_jira_status(client): - log.warning('The JIRA server looks like its down. Shutting down...') + if not config["sync2jira"]["develop"] and not check_jira_status(client): + log.warning("The JIRA server looks like its down. Shutting down...") raise JIRAError log.info("Looking for dupes of upstream %s, %s", issue.url, issue.title) diff --git a/sync2jira/downstream_pr.py b/sync2jira/downstream_pr.py index 5c46d1b6..0663c3e2 100644 --- a/sync2jira/downstream_pr.py +++ b/sync2jira/downstream_pr.py @@ -26,7 +26,7 @@ import sync2jira.downstream_issue as d_issue from sync2jira.intermediary import Issue, matcher -log = logging.getLogger('sync2jira') +log = logging.getLogger("sync2jira") def format_comment(pr, pr_suffix, client): @@ -49,15 +49,17 @@ def format_comment(pr, pr_suffix, client): else: reporter = pr.reporter - if 'closed' in pr_suffix: + if "closed" in pr_suffix: comment = f"Merge request [{pr.title}| {pr.url}] was closed." - elif 'reopened' in pr_suffix: + elif "reopened" in pr_suffix: comment = f"Merge request [{pr.title}| {pr.url}] was reopened." - elif 'merged' in pr_suffix: + elif "merged" in pr_suffix: comment = f"Merge request [{pr.title}| {pr.url}] was merged!" else: - comment = f"{reporter} mentioned this issue in " \ + comment = ( + f"{reporter} mentioned this issue in " f"merge request [{pr.title}| {pr.url}]." + ) return comment @@ -105,7 +107,7 @@ def update_jira_issue(existing, pr, client): :returns: Nothing """ # Get our updates array - updates = pr.downstream.get('pr_updates', {}) + updates = pr.downstream.get("pr_updates", {}) # Format and add comment to indicate PR has been linked new_comment = format_comment(pr, pr.suffix, client) @@ -122,16 +124,19 @@ def update_jira_issue(existing, pr, client): d_issue.attach_link(client, existing, remote_link) # Only synchronize link_transition for listings that op-in - if any('merge_transition' in item for item in updates) and 'merged' in pr.suffix: + if any("merge_transition" in item for item in updates) and "merged" in pr.suffix: log.info("Looking for new merged_transition") - update_transition(client, existing, pr, 'merge_transition') + update_transition(client, existing, pr, "merge_transition") # Only synchronize merge_transition for listings that op-in # and a link comment has been created - if any('link_transition' in item for item in updates) and \ - 'mentioned' in new_comment and not exists: + if ( + any("link_transition" in item for item in updates) + and "mentioned" in new_comment + and not exists + ): log.info("Looking for new link_transition") - update_transition(client, existing, pr, 'link_transition') + update_transition(client, existing, pr, "link_transition") def update_transition(client, existing, pr, transition_type): @@ -145,7 +150,9 @@ def update_transition(client, existing, pr, transition_type): :returns: Nothing """ # Get our closed status - closed_status = next(filter(lambda d: transition_type in d, pr.downstream.get('pr_updates', {})))[transition_type] + closed_status = next( + filter(lambda d: transition_type in d, pr.downstream.get("pr_updates", {})) + )[transition_type] # Update the state d_issue.change_status(client, existing, closed_status, pr) @@ -165,7 +172,7 @@ def sync_with_jira(pr, config): log.info("[PR] Considering upstream %s, %s", pr.url, pr.title) # Return if testing - if config['sync2jira']['testing']: + if config["sync2jira"]["testing"]: log.info("Testing flag is true. Skipping actual update.") return None @@ -177,8 +184,8 @@ def sync_with_jira(pr, config): client = d_issue.get_jira_client(pr, config) # Check the status of the JIRA client - if not config['sync2jira']['develop'] and not d_issue.check_jira_status(client): - log.warning('The JIRA server looks like its down. Shutting down...') + if not config["sync2jira"]["develop"] and not d_issue.check_jira_status(client): + log.warning("The JIRA server looks like its down. Shutting down...") raise JIRAError # Find our JIRA issue if one exists @@ -190,11 +197,11 @@ def sync_with_jira(pr, config): response = client.search_issues(query) # Throw error and return if nothing could be found if len(response) == 0 or len(response) > 1: - log.warning(f'No JIRA issue could be found for {pr.title}') + log.warning(f"No JIRA issue could be found for {pr.title}") return except JIRAError: # If no issue exists, it will throw a JIRA error - log.warning(f'No JIRA issue exists for PR: {pr.title}. Query: {query}') + log.warning(f"No JIRA issue exists for PR: {pr.title}. Query: {query}") return # Existing JIRA issue is the only one in the query diff --git a/sync2jira/intermediary.py b/sync2jira/intermediary.py index fd2c5083..b36c66fd 100644 --- a/sync2jira/intermediary.py +++ b/sync2jira/intermediary.py @@ -22,9 +22,26 @@ class Issue(object): """Issue Intermediary object""" - def __init__(self, source, title, url, upstream, comments, - config, tags, fixVersion, priority, content, - reporter, assignee, status, id, storypoints, upstream_id, downstream=None): + def __init__( + self, + source, + title, + url, + upstream, + comments, + config, + tags, + fixVersion, + priority, + content, + reporter, + assignee, + status, + id, + storypoints, + upstream_id, + downstream=None, + ): self.source = source self._title = title[:254] self.url = url @@ -40,10 +57,10 @@ def __init__(self, source, title, url, upstream, comments, # JIRA treats utf-8 characters in ways we don't totally understand, so scrub content down to # simple ascii characters right from the start. - self.content = self.content.encode('ascii', errors='replace').decode('ascii') + self.content = self.content.encode("ascii", errors="replace").decode("ascii") # We also apply this content in regexs to pattern match, so remove any escape characters - self.content = self.content.replace('\\', '') + self.content = self.content.replace("\\", "") self.reporter = reporter self.assignee = assignee @@ -51,13 +68,13 @@ def __init__(self, source, title, url, upstream, comments, self.id = str(id) self.upstream_id = upstream_id if not downstream: - self.downstream = config['sync2jira']['map'][self.source][upstream] + self.downstream = config["sync2jira"]["map"][self.source][upstream] else: self.downstream = downstream @property def title(self): - _title = u'[%s] %s' % (self.upstream, self._title) + _title = "[%s] %s" % (self.upstream, self._title) return _title[:254].strip() @property @@ -67,49 +84,53 @@ def upstream_title(self): @classmethod def from_github(cls, upstream, issue, config): """Helper function to create intermediary object.""" - upstream_source = 'github' + upstream_source = "github" comments = [] - for comment in issue['comments']: - comments.append({ - 'author': comment['author'], - 'name': comment['name'], - 'body': trim_string(comment['body']), - 'id': comment['id'], - 'date_created': comment['date_created'], - 'changed': None - }) + for comment in issue["comments"]: + comments.append( + { + "author": comment["author"], + "name": comment["name"], + "body": trim_string(comment["body"]), + "id": comment["id"], + "date_created": comment["date_created"], + "changed": None, + } + ) # Reformat the state field - if issue['state']: - if issue['state'] == 'open': - issue['state'] = 'Open' - elif issue['state'] == 'closed': - issue['state'] = 'Closed' + if issue["state"]: + if issue["state"] == "open": + issue["state"] = "Open" + elif issue["state"] == "closed": + issue["state"] = "Closed" # Perform any mapping - mapping = config['sync2jira']['map'][upstream_source][upstream].get('mapping', []) + mapping = config["sync2jira"]["map"][upstream_source][upstream].get( + "mapping", [] + ) # Check for fixVersion - if any('fixVersion' in item for item in mapping): + if any("fixVersion" in item for item in mapping): map_fixVersion(mapping, issue) return cls( source=upstream_source, - title=issue['title'], - url=issue['html_url'], + title=issue["title"], + url=issue["html_url"], upstream=upstream, config=config, comments=comments, - tags=issue['labels'], - fixVersion=[issue['milestone']], - priority=issue.get('priority'), - content=issue['body'] or '', - reporter=issue['user'], - assignee=issue['assignees'], - status=issue['state'], - id=issue['id'], - storypoints=issue.get('storypoints'), - upstream_id=issue['number'] + tags=issue["labels"], + fixVersion=[issue["milestone"]], + priority=issue.get("priority"), + content=issue["body"] or "", + reporter=issue["user"], + assignee=issue["assignees"], + status=issue["state"], + id=issue["id"], + storypoints=issue.get("storypoints"), + upstream_id=issue["number"], ) def __repr__(self): @@ -119,9 +140,25 @@ def __repr__(self): class PR(object): """PR intermediary object""" - def __init__(self, source, jira_key, title, url, upstream, config, - comments, priority, content, reporter, - assignee, status, id, suffix, match, downstream=None): + def __init__( + self, + source, + jira_key, + title, + url, + upstream, + config, + comments, + priority, + content, + reporter, + assignee, + status, + id, + suffix, + match, + downstream=None, + ): self.source = source self.jira_key = jira_key self._title = title[:254] @@ -138,10 +175,12 @@ def __init__(self, source, jira_key, title, url, upstream, config, # First trim the size of the content self.content = trim_string(content) - self.content = self.content.encode('ascii', errors='replace').decode('ascii') + self.content = self.content.encode("ascii", errors="replace").decode( + "ascii" + ) # We also apply this content in regexs to pattern match, so remove any escape characters - self.content = self.content.replace('\\', '') + self.content = self.content.replace("\\", "") else: self.content = None @@ -154,54 +193,56 @@ def __init__(self, source, jira_key, title, url, upstream, config, # self.upstream_id = upstream_id if not downstream: - self.downstream = config['sync2jira']['map'][self.source][upstream] + self.downstream = config["sync2jira"]["map"][self.source][upstream] else: self.downstream = downstream return @property def title(self): - return u'[%s] %s' % (self.upstream, self._title) + return "[%s] %s" % (self.upstream, self._title) @classmethod def from_github(cls, upstream, pr, suffix, config): """Helper function to create intermediary object.""" # Set our upstream source - upstream_source = 'github' + upstream_source = "github" # Format our comments comments = [] - for comment in pr['comments']: - comments.append({ - 'author': comment['author'], - 'name': comment['name'], - 'body': trim_string(comment['body']), - 'id': comment['id'], - 'date_created': comment['date_created'], - 'changed': None - }) + for comment in pr["comments"]: + comments.append( + { + "author": comment["author"], + "name": comment["name"], + "body": trim_string(comment["body"]), + "id": comment["id"], + "date_created": comment["date_created"], + "changed": None, + } + ) # Build our URL - url = pr['html_url'] + url = pr["html_url"] # Match to a JIRA match = matcher(pr.get("body"), comments) # Figure out what state we're transitioning too - if 'reopened' in suffix: - suffix = 'reopened' - elif 'closed' in suffix: + if "reopened" in suffix: + suffix = "reopened" + elif "closed" in suffix: # Check if we're merging or closing - if pr['merged']: - suffix = 'merged' + if pr["merged"]: + suffix = "merged" else: - suffix = 'closed' + suffix = "closed" # Return our PR object return cls( source=upstream_source, jira_key=match, - title=pr['title'], + title=pr["title"], url=url, upstream=upstream, config=config, @@ -209,12 +250,12 @@ def from_github(cls, upstream, pr, suffix, config): # tags=issue['labels'], # fixVersion=[issue['milestone']], priority=None, - content=pr.get('body'), - reporter=pr['user']['fullname'], - assignee=pr['assignee'], + content=pr.get("body"), + reporter=pr["user"]["fullname"], + assignee=pr["assignee"], # GitHub PRs do not have status status=None, - id=pr['number'], + id=pr["number"], # upstream_id=issue['number'], suffix=suffix, match=match, @@ -229,11 +270,11 @@ def map_fixVersion(mapping, issue): :param Dict issue: Upstream issue object """ # Get our fixVersion mapping - fixVersion_map = next(filter(lambda d: "fixVersion" in d, mapping))['fixVersion'] + fixVersion_map = next(filter(lambda d: "fixVersion" in d, mapping))["fixVersion"] # Now update the fixVersion - if issue['milestone']: - issue['milestone'] = fixVersion_map.replace('XXX', issue['milestone']) + if issue["milestone"]: + issue["milestone"] = fixVersion_map.replace("XXX", issue["milestone"]) def matcher(content, comments): @@ -255,8 +296,7 @@ def matcher(content, comments): # Parse to extract the JIRA information. 2 types of matches: # 1 - To match to JIRA issue (i.e. Relates to JIRA: FACTORY-1234) # 2 - To match to upstream issue (i.e. Relates to Issue: !5) - match_jira = re.findall(r"Relates to JIRA: ([\w]*-[\d]*)", - all_data) + match_jira = re.findall(r"Relates to JIRA: ([\w]*-[\d]*)", all_data) if match_jira: for match in match_jira: # Assert that the match was correct diff --git a/sync2jira/mailer.py b/sync2jira/mailer.py index ecd7296e..302f751a 100644 --- a/sync2jira/mailer.py +++ b/sync2jira/mailer.py @@ -8,8 +8,8 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -DEFAULT_FROM = os.environ.get('DEFAULT_FROM') -DEFAULT_SERVER = os.environ.get('DEFAULT_SERVER') +DEFAULT_FROM = os.environ.get("DEFAULT_FROM") +DEFAULT_SERVER = os.environ.get("DEFAULT_SERVER") def send_mail(recipients, subject, text, cc): @@ -27,14 +27,14 @@ def send_mail(recipients, subject, text, cc): _cfg.setdefault("server", DEFAULT_SERVER) _cfg.setdefault("from", DEFAULT_FROM) sender = _cfg["from"] - msg = MIMEMultipart('related') + msg = MIMEMultipart("related") msg["Subject"] = subject msg["From"] = sender msg["To"] = ", ".join(recipients) if cc: - msg['Cc'] = ", ".join(cc) + msg["Cc"] = ", ".join(cc) server = smtplib.SMTP(_cfg["server"]) - part = MIMEText(text, 'html', 'utf-8') + part = MIMEText(text, "html", "utf-8") msg.attach(part) server.sendmail(sender, recipients, msg.as_string()) server.quit() diff --git a/sync2jira/main.py b/sync2jira/main.py index 86034764..0a834011 100644 --- a/sync2jira/main.py +++ b/sync2jira/main.py @@ -49,10 +49,10 @@ logging.basicConfig(format=FORMAT, level=logging.INFO) logging.basicConfig(format=FORMAT, level=logging.DEBUG) logging.basicConfig(format=FORMAT, level=logging.WARNING) -log = logging.getLogger('sync2jira') +log = logging.getLogger("sync2jira") # Only allow fedmsg logs that are critical -fedmsg_log = logging.getLogger('fedmsg.crypto.utils') +fedmsg_log = logging.getLogger("fedmsg.crypto.utils") fedmsg_log.setLevel(50) remote_link_title = "Upstream issue" @@ -62,37 +62,37 @@ issue_handlers = { # GitHub # New webhook-2fm topics - 'github.issues': u_issue.handle_github_message, - 'github.issue_comment': u_issue.handle_github_message, + "github.issues": u_issue.handle_github_message, + "github.issue_comment": u_issue.handle_github_message, # Old github2fedmsg topics - 'github.issue.opened': u_issue.handle_github_message, - 'github.issue.reopened': u_issue.handle_github_message, - 'github.issue.labeled': u_issue.handle_github_message, - 'github.issue.assigned': u_issue.handle_github_message, - 'github.issue.unassigned': u_issue.handle_github_message, - 'github.issue.closed': u_issue.handle_github_message, - 'github.issue.comment': u_issue.handle_github_message, - 'github.issue.unlabeled': u_issue.handle_github_message, - 'github.issue.milestoned': u_issue.handle_github_message, - 'github.issue.demilestoned': u_issue.handle_github_message, - 'github.issue.edited': u_issue.handle_github_message, + "github.issue.opened": u_issue.handle_github_message, + "github.issue.reopened": u_issue.handle_github_message, + "github.issue.labeled": u_issue.handle_github_message, + "github.issue.assigned": u_issue.handle_github_message, + "github.issue.unassigned": u_issue.handle_github_message, + "github.issue.closed": u_issue.handle_github_message, + "github.issue.comment": u_issue.handle_github_message, + "github.issue.unlabeled": u_issue.handle_github_message, + "github.issue.milestoned": u_issue.handle_github_message, + "github.issue.demilestoned": u_issue.handle_github_message, + "github.issue.edited": u_issue.handle_github_message, } # PR related handlers pr_handlers = { # GitHub # New webhook-2fm topics - 'github.pull_request': u_pr.handle_github_message, - 'github.issue_comment': u_pr.handle_github_message, + "github.pull_request": u_pr.handle_github_message, + "github.issue_comment": u_pr.handle_github_message, # Old github2fedmsg topics - 'github.pull_request.opened': u_pr.handle_github_message, - 'github.pull_request.edited': u_pr.handle_github_message, - 'github.issue.comment': u_pr.handle_github_message, - 'github.pull_request.reopened': u_pr.handle_github_message, - 'github.pull_request.closed': u_pr.handle_github_message, + "github.pull_request.opened": u_pr.handle_github_message, + "github.pull_request.edited": u_pr.handle_github_message, + "github.issue.comment": u_pr.handle_github_message, + "github.pull_request.reopened": u_pr.handle_github_message, + "github.pull_request.closed": u_pr.handle_github_message, } DATAGREPPER_URL = "http://apps.fedoraproject.org/datagrepper/raw" -INITIALIZE = os.getenv('INITIALIZE', '0') +INITIALIZE = os.getenv("INITIALIZE", "0") def load_config(loader=fedmsg.config.load_config): @@ -107,39 +107,42 @@ def load_config(loader=fedmsg.config.load_config): config = loader() # Force some vars that we like - config['mute'] = True + config["mute"] = True # debug mode - if config.get('sync2jira', {}).get('debug', False): - handler = logging.FileHandler('sync2jira_main.log') + if config.get("sync2jira", {}).get("debug", False): + handler = logging.FileHandler("sync2jira_main.log") log.addHandler(handler) log.setLevel(logging.DEBUG) # Validate it - if 'sync2jira' not in config: + if "sync2jira" not in config: raise ValueError("No sync2jira section found in fedmsg.d/ config") - if 'map' not in config['sync2jira']: + if "map" not in config["sync2jira"]: raise ValueError("No sync2jira.map section found in fedmsg.d/ config") - possible = {'github'} - specified = set(config['sync2jira']['map'].keys()) + possible = {"github"} + specified = set(config["sync2jira"]["map"].keys()) if not specified.issubset(possible): message = "Specified handlers: %s, must be a subset of %s." - raise ValueError(message % ( - ", ".join(f'"{item}"' for item in specified), - ", ".join(f'"{item}"' for item in possible), - )) - - if 'jira' not in config['sync2jira']: + raise ValueError( + message + % ( + ", ".join(f'"{item}"' for item in specified), + ", ".join(f'"{item}"' for item in possible), + ) + ) + + if "jira" not in config["sync2jira"]: raise ValueError("No sync2jira.jira section found in fedmsg.d/ config") # Provide some default values defaults = { - 'listen': True, + "listen": True, } for key, value in defaults.items(): - config['sync2jira'][key] = config['sync2jira'].get(key, value) + config["sync2jira"][key] = config["sync2jira"].get(key, value) return config @@ -153,14 +156,14 @@ def listen(config): :param Dict config: Config dict :returns: Nothing """ - if not config['sync2jira'].get('listen'): + if not config["sync2jira"].get("listen"): log.info("`listen` is disabled. Exiting.") return log.info("Waiting for a relevant fedmsg message to arrive...") for _, _, topic, msg in fedmsg.tail_messages(**config): - idx = msg['msg_id'] - suffix = ".".join(topic.split('.')[3:]) + idx = msg["msg_id"] + suffix = ".".join(topic.split(".")[3:]) log.debug("Encountered %r %r %r", suffix, topic, idx) if suffix not in issue_handlers and suffix not in pr_handlers: @@ -184,10 +187,10 @@ def initialize_issues(config, testing=False, repo_name=None): :returns: Nothing """ log.info("Running initialization to sync all issues from upstream to jira") - log.info("Testing flag is %r", config['sync2jira']['testing']) - mapping = config['sync2jira']['map'] - for upstream in mapping.get('github', {}).keys(): - if 'issue' not in mapping.get('github', {}).get(upstream, {}).get('sync', []): + log.info("Testing flag is %r", config["sync2jira"]["testing"]) + mapping = config["sync2jira"]["map"] + for upstream in mapping.get("github", {}).keys(): + if "issue" not in mapping.get("github", {}).get(upstream, {}).get("sync", []): continue if repo_name is not None and upstream != repo_name: continue @@ -209,7 +212,7 @@ def initialize_issues(config, testing=False, repo_name=None): initialize_issues(config) return else: - if not config['sync2jira']['develop']: + if not config["sync2jira"]["develop"]: # Only send the failure email if we are not developing report_failure(config) raise @@ -229,10 +232,12 @@ def initialize_pr(config, testing=False, repo_name=None): :returns: Nothing """ log.info("Running initialization to sync all PRs from upstream to jira") - log.info("Testing flag is %r", config['sync2jira']['testing']) - mapping = config['sync2jira']['map'] - for upstream in mapping.get('github', {}).keys(): - if 'pullrequest' not in mapping.get('github', {}).get(upstream, {}).get('sync', []): + log.info("Testing flag is %r", config["sync2jira"]["testing"]) + mapping = config["sync2jira"]["map"] + for upstream in mapping.get("github", {}).keys(): + if "pullrequest" not in mapping.get("github", {}).get(upstream, {}).get( + "sync", [] + ): continue if repo_name is not None and upstream != repo_name: continue @@ -255,7 +260,7 @@ def initialize_pr(config, testing=False, repo_name=None): initialize_pr(config) return else: - if not config['sync2jira']['develop']: + if not config["sync2jira"]["develop"]: # Only send the failure email if we are not developing report_failure(config) raise @@ -270,22 +275,22 @@ def initialize_recent(config): :return: Nothing """ # Query datagrepper - ret = query(category=['github'], delta=int(600), rows_per_page=100) + ret = query(category=["github"], delta=int(600), rows_per_page=100) # Loop and sync for entry in ret: # Extract our topic - suffix = ".".join(entry['topic'].split('.')[3:]) - log.debug("Encountered %r %r", suffix, entry['topic']) + suffix = ".".join(entry["topic"].split(".")[3:]) + log.debug("Encountered %r %r", suffix, entry["topic"]) # Disregard if it's invalid if suffix not in issue_handlers and suffix not in pr_handlers: continue # Deal with the message - log.debug("Handling %r %r", suffix, entry['topic']) - msg = entry['msg'] - handle_msg({'msg': msg}, suffix, config) + log.debug("Handling %r %r", suffix, entry["topic"]) + msg = entry["msg"] + handle_msg({"msg": msg}, suffix, config) def handle_msg(msg, suffix, config): @@ -299,16 +304,16 @@ def handle_msg(msg, suffix, config): pr = None # GitHub '.issue.' is used for both PR and Issue # Check for that edge case - if suffix == 'github.issue.comment': - if 'pull_request' in msg['msg']['issue'] and msg['msg']['action'] != 'deleted': + if suffix == "github.issue.comment": + if "pull_request" in msg["msg"]["issue"] and msg["msg"]["action"] != "deleted": # pr_filter turns on/off the filtering of PRs pr = issue_handlers[suffix](msg, config, pr_filter=False) if not pr: return # Issues do not have suffix and reporter needs to be reformatted pr.suffix = suffix - pr.reporter = pr.reporter.get('fullname') - setattr(pr, 'match', matcher(pr.content, pr.comments)) + pr.reporter = pr.reporter.get("fullname") + setattr(pr, "match", matcher(pr.content, pr.comments)) else: issue = issue_handlers[suffix](msg, config) elif suffix in issue_handlers: @@ -336,7 +341,7 @@ def query(limit=None, **kwargs): params = deepcopy(kwargs) # Important to set ASC order when paging to avoid duplicates - params['order'] = 'asc' + params["order"] = "asc" # Fetch results: # - once, if limit is 0 or None (the default) @@ -346,25 +351,31 @@ def query(limit=None, **kwargs): total = limit or 1 while fetched < total: results = get(params=params) - count = results['count'] + count = results["count"] # Exit the loop if there was nothing to fetch if count <= 0: break fetched += count - for result in results['raw_messages']: + for result in results["raw_messages"]: yield result - params['page'] = params.get('page', 1) + 1 + params["page"] = params.get("page", 1) + 1 def get(params): url = DATAGREPPER_URL - headers = {'Accept': 'application/json', } + headers = { + "Accept": "application/json", + } - response = requests.get(url=url, params=params, headers=headers, - auth=HTTPKerberosAuth(mutual_authentication=OPTIONAL)) + response = requests.get( + url=url, + params=params, + headers=headers, + auth=HTTPKerberosAuth(mutual_authentication=OPTIONAL), + ) return response.json() @@ -382,10 +393,10 @@ def main(runtime_test=False, runtime_config=None): logging.basicConfig(level=logging.INFO) warnings.simplefilter("ignore") - config['validate_signatures'] = False + config["validate_signatures"] = False try: - if str(INITIALIZE) == '1': + if str(INITIALIZE) == "1": log.info("Initialization True") # Initialize issues log.info("Initializing Issues...") @@ -403,7 +414,7 @@ def main(runtime_test=False, runtime_config=None): except KeyboardInterrupt: pass except: # noqa: E722 - if not config['sync2jira']['develop']: + if not config["sync2jira"]["develop"]: # Only send the failure email if we are not developing report_failure(config) raise @@ -417,16 +428,19 @@ def report_failure(config): """ # Email our admins with the traceback template_loader = jinja2.FileSystemLoader( - searchpath='usr/local/src/sync2jira/sync2jira/') + searchpath="usr/local/src/sync2jira/sync2jira/" + ) template_env = jinja2.Environment(loader=template_loader, autoescape=True) - template = template_env.get_template('failure_template.jinja') + template = template_env.get_template("failure_template.jinja") html_text = template.render(traceback=traceback.format_exc()) # Send mail - send_mail(recipients=[config['sync2jira']['mailing-list']], - cc=None, - subject=failure_email_subject, - text=html_text) + send_mail( + recipients=[config["sync2jira"]["mailing-list"]], + cc=None, + subject=failure_email_subject, + text=html_text, + ) def list_managed(): @@ -436,10 +450,10 @@ def list_managed(): :return: Nothing """ config = load_config() - mapping = config['sync2jira']['map'] + mapping = config["sync2jira"]["map"] warnings.simplefilter("ignore") - for upstream in mapping.get('github', {}).keys(): + for upstream in mapping.get("github", {}).keys(): for issue in u_issue.github_issues(upstream, config): print(issue.url) @@ -452,11 +466,11 @@ def close_duplicates(): """ config = load_config() logging.basicConfig(level=logging.INFO) - log.info("Testing flag is %r", config['sync2jira']['testing']) - mapping = config['sync2jira']['map'] + log.info("Testing flag is %r", config["sync2jira"]["testing"]) + mapping = config["sync2jira"]["map"] warnings.simplefilter("ignore") - for upstream in mapping.get('github', {}).keys(): + for upstream in mapping.get("github", {}).keys(): for issue in u_issue.github_issues(upstream, config): try: d_issue.close_duplicates(issue, config) @@ -466,5 +480,5 @@ def close_duplicates(): log.info("Done with GitHub duplicates.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/sync2jira/upstream_issue.py b/sync2jira/upstream_issue.py index 455f8e64..01e2fdcf 100644 --- a/sync2jira/upstream_issue.py +++ b/sync2jira/upstream_issue.py @@ -26,10 +26,10 @@ import sync2jira.intermediary as i -log = logging.getLogger('sync2jira') -graphqlurl = 'https://api.github.com/graphql' +log = logging.getLogger("sync2jira") +graphqlurl = "https://api.github.com/graphql" -ghquery = ''' +ghquery = """ query MyQuery( $orgname: String!, $reponame: String!, $issuenumber: Int! ) { @@ -107,7 +107,7 @@ } } } -''' +""" def handle_github_message(msg, config, pr_filter=True): @@ -120,38 +120,38 @@ def handle_github_message(msg, config, pr_filter=True): :returns: Issue object :rtype: sync2jira.intermediary.Issue """ - owner = msg['msg']['repository']['owner']['login'] - repo = msg['msg']['repository']['name'] - upstream = '{owner}/{repo}'.format(owner=owner, repo=repo) + owner = msg["msg"]["repository"]["owner"]["login"] + repo = msg["msg"]["repository"]["name"] + upstream = "{owner}/{repo}".format(owner=owner, repo=repo) - mapped_repos = config['sync2jira']['map']['github'] + mapped_repos = config["sync2jira"]["map"]["github"] if upstream not in mapped_repos: log.debug("%r not in Github map: %r", upstream, mapped_repos.keys()) return None - elif 'issue' not in mapped_repos[upstream].get('sync', {}) and pr_filter is True: + elif "issue" not in mapped_repos[upstream].get("sync", {}) and pr_filter is True: log.debug("%r not in Github Issue map: %r", upstream, mapped_repos.keys()) return None - elif 'pullrequest' not in mapped_repos[upstream].get('sync', {}) and pr_filter is False: + elif ( + "pullrequest" not in mapped_repos[upstream].get("sync", {}) + and pr_filter is False + ): log.debug("%r not in Github PR map: %r", upstream, mapped_repos.keys()) return None - _filter = config['sync2jira']\ - .get('filters', {})\ - .get('github', {})\ - .get(upstream, {}) + _filter = config["sync2jira"].get("filters", {}).get("github", {}).get(upstream, {}) - issue = msg['msg']['issue'] + issue = msg["msg"]["issue"] for key, expected in _filter.items(): - if key == 'labels': + if key == "labels": # special handling for label: we look for it in the list of msg labels - actual = {label['name'] for label in issue['labels']} + actual = {label["name"] for label in issue["labels"]} if actual.isdisjoint(expected): log.debug("Labels %s not found on issue: %s", expected, upstream) return None - elif key == 'milestone': + elif key == "milestone": # special handling for milestone: use the number milestone = issue.get(key, {}) - actual = milestone.get('number') + actual = milestone.get("number") if expected != actual: log.debug("Milestone %s not set on issue: %s", expected, upstream) return None @@ -159,16 +159,23 @@ def handle_github_message(msg, config, pr_filter=True): # direct comparison actual = issue.get(key) if actual != expected: - log.debug("Actual %r %r != expected %r on issue %s", - key, actual, expected, upstream) + log.debug( + "Actual %r %r != expected %r on issue %s", + key, + actual, + expected, + upstream, + ) return None - if pr_filter and 'pull_request' in issue and 'closed_at' not in issue: - log.debug("%r is a pull request. Ignoring.", issue.get('html_url', '')) + if pr_filter and "pull_request" in issue and "closed_at" not in issue: + log.debug( + "%r is a pull request. Ignoring.", issue.get("html_url", "") + ) return None - token = config['sync2jira'].get('github_token') - headers = {'Authorization': 'token ' + token} if token else {} + token = config["sync2jira"].get("github_token") + headers = {"Authorization": "token " + token} if token else {} github_client = Github(token, retry=5) reformat_github_issue(issue, upstream, github_client) add_project_values(issue, upstream, headers, config) @@ -184,16 +191,19 @@ def github_issues(upstream, config): :returns: a generator for GitHub Issue objects :rtype: Generator[sync2jira.intermediary.Issue] """ - token = config['sync2jira'].get('github_token') - headers = {'Authorization': 'token ' + token} if token else {} + token = config["sync2jira"].get("github_token") + headers = {"Authorization": "token " + token} if token else {} github_client = Github(token, retry=5) - for issue in generate_github_items('issues', upstream, config): - if 'pull_request' in issue or '/pull/' in issue.get('html_url', ''): + for issue in generate_github_items("issues", upstream, config): + if "pull_request" in issue or "/pull/" in issue.get("html_url", ""): # We don't want to copy these around - orgname, reponame = upstream.rsplit('/', 1) + orgname, reponame = upstream.rsplit("/", 1) log.debug( "Issue %s/%s#%s is a pull request; skipping", - orgname, reponame, issue['number']) + orgname, + reponame, + issue["number"], + ) continue reformat_github_issue(issue, upstream, github_client) add_project_values(issue, upstream, headers, config) @@ -208,55 +218,66 @@ def add_project_values(issue, upstream, headers, config): :param dict headers: HTTP Request headers, including access token, if any :param dict config: Config """ - upstream_config = config['sync2jira']['map']['github'][upstream] - project_number = upstream_config.get('github_project_number') - issue_updates = upstream_config.get('issue_updates', {}) - if 'github_project_fields' not in issue_updates: + upstream_config = config["sync2jira"]["map"]["github"][upstream] + project_number = upstream_config.get("github_project_number") + issue_updates = upstream_config.get("issue_updates", {}) + if "github_project_fields" not in issue_updates: return - issue['storypoints'] = None - issue['priority'] = None - issuenumber = issue['number'] - github_project_fields = upstream_config['github_project_fields'] - orgname, reponame = upstream.rsplit('/', 1) - variables = { - "orgname": orgname, - "reponame": reponame, - "issuenumber": issuenumber} + issue["storypoints"] = None + issue["priority"] = None + issuenumber = issue["number"] + github_project_fields = upstream_config["github_project_fields"] + orgname, reponame = upstream.rsplit("/", 1) + variables = {"orgname": orgname, "reponame": reponame, "issuenumber": issuenumber} response = requests.post( - graphqlurl, - headers=headers, - json={"query": ghquery, "variables": variables}) + graphqlurl, headers=headers, json={"query": ghquery, "variables": variables} + ) if response.status_code != 200: - log.info("HTTP error while fetching issue %s/%s#%s: %s", - orgname, reponame, issuenumber, response.text) + log.info( + "HTTP error while fetching issue %s/%s#%s: %s", + orgname, + reponame, + issuenumber, + response.text, + ) return data = response.json() - gh_issue = data.get('data', {}).get('repository', {}).get('issue') + gh_issue = data.get("data", {}).get("repository", {}).get("issue") if not gh_issue: - log.info("GitHub error while fetching issue %s/%s#%s: %s", - orgname, reponame, issuenumber, response.text) + log.info( + "GitHub error while fetching issue %s/%s#%s: %s", + orgname, + reponame, + issuenumber, + response.text, + ) return project_node = _get_current_project_node( - upstream, project_number, issuenumber, gh_issue) + upstream, project_number, issuenumber, gh_issue + ) if not project_node: return - item_nodes = project_node.get('fieldValues', {}).get('nodes', {}) + item_nodes = project_node.get("fieldValues", {}).get("nodes", {}) for item in item_nodes: - gh_field_name = item.get('fieldName', {}).get('name') + gh_field_name = item.get("fieldName", {}).get("name") if not gh_field_name: continue - prio_field = github_project_fields.get('priority', {}).get('gh_field') + prio_field = github_project_fields.get("priority", {}).get("gh_field") if gh_field_name == prio_field: - issue['priority'] = item.get('name') + issue["priority"] = item.get("name") continue - sp_field = github_project_fields.get('storypoints', {}).get('gh_field') + sp_field = github_project_fields.get("storypoints", {}).get("gh_field") if gh_field_name == sp_field: try: - issue['storypoints'] = int(item['number']) + issue["storypoints"] = int(item["number"]) except (ValueError, KeyError) as err: log.info( "Error while processing storypoints for issue %s/%s#%s: %s", - orgname, reponame, issuenumber, err) + orgname, + reponame, + issuenumber, + err, + ) continue @@ -265,13 +286,13 @@ def reformat_github_issue(issue, upstream, github_client): # Update comments: # If there are no comments just make an empty array - if not issue['comments']: - issue['comments'] = [] + if not issue["comments"]: + issue["comments"] = [] else: # We have multiple comments and need to make api call to get them repo = github_client.get_repo(upstream) - github_issue = repo.get_issue(number=issue['number']) - issue['comments'] = reformat_github_comments(github_issue.get_comments()) + github_issue = repo.get_issue(number=issue["number"]) + issue["comments"] = reformat_github_comments(github_issue.get_comments()) # Update the rest of the parts reformat_github_common(issue, github_client) @@ -281,49 +302,50 @@ def reformat_github_comments(comments): """Helper function which encapsulates reformatting comments""" return [ { - 'author': comment.user.name or comment.user.login, - 'name': comment.user.login, - 'body': comment.body, - 'id': comment.id, - 'date_created': comment.created_at, - 'changed': None - } for comment in comments + "author": comment.user.name or comment.user.login, + "name": comment.user.login, + "body": comment.body, + "id": comment.id, + "date_created": comment.created_at, + "changed": None, + } + for comment in comments ] def reformat_github_common(item, github_client): """Helper function which tweaks the data format of the parts of Issues and - PRs which are common so that they better match Pagure + PRs which are common so that they better match Pagure """ # Update reporter: # Search for the user - reporter = github_client.get_user(item['user']['login']) + reporter = github_client.get_user(item["user"]["login"]) # Update the reporter field in the message (to match Pagure format) if reporter.name: - item['user']['fullname'] = reporter.name + item["user"]["fullname"] = reporter.name else: - item['user']['fullname'] = item['user']['login'] + item["user"]["fullname"] = item["user"]["login"] # Update assignee(s): assignees = [] - for person in item.get('assignees', []): - assignee = github_client.get_user(person['login']) - assignees.append({'fullname': assignee.name}) + for person in item.get("assignees", []): + assignee = github_client.get_user(person["login"]) + assignees.append({"fullname": assignee.name}) # Update the assignee field in the message (to match Pagure format) - item['assignees'] = assignees + item["assignees"] = assignees # Update the label field in the message (to match Pagure format) - if item['labels']: + if item["labels"]: # Loop through all the labels on GitHub and add them # to the new label list and then reassign the message new_label = [] - for label in item['labels']: - new_label.append(label['name']) - item['labels'] = new_label + for label in item["labels"]: + new_label.append(label["name"]) + item["labels"] = new_label # Update the milestone field in the message (to match Pagure format) - if item.get('milestone'): - item['milestone'] = item['milestone']['title'] + if item.get("milestone"): + item["milestone"] = item["milestone"]["title"] def generate_github_items(api_method, upstream, config): @@ -336,39 +358,37 @@ def generate_github_items(api_method, upstream, config): :returns: a generator for GitHub Issue/PR objects :rtype: Generator[Any, Any, None] """ - token = config['sync2jira'].get('github_token') + token = config["sync2jira"].get("github_token") if not token: headers = {} - log.warning('No github_token found. We will be rate-limited...') + log.warning("No github_token found. We will be rate-limited...") else: - headers = {'Authorization': 'token ' + token} + headers = {"Authorization": "token " + token} - params = config['sync2jira']\ - .get('filters', {})\ - .get('github', {})\ - .get(upstream, {}) + params = config["sync2jira"].get("filters", {}).get("github", {}).get(upstream, {}) - url = 'https://api.github.com/repos/' + upstream + '/' + api_method + url = "https://api.github.com/repos/" + upstream + "/" + api_method if params: - labels = params.get('labels') + labels = params.get("labels") if isinstance(labels, list): # We have to flatten the labels list to a comma-separated string, # so make a copy to avoid mutating the config object url_filter = deepcopy(params) - url_filter['labels'] = ','.join(labels) + url_filter["labels"] = ",".join(labels) else: url_filter = params # Use the existing filter, unmodified - url += '?' + urlencode(url_filter) + url += "?" + urlencode(url_filter) return get_all_github_data(url, headers) def _get_current_project_node(upstream, project_number, issue_number, gh_issue): - project_items = gh_issue['projectItems']['nodes'] + project_items = gh_issue["projectItems"]["nodes"] # If there are no project items, there is nothing to return. if len(project_items) == 0: - log.debug("Issue %s#%s is not associated with any project", - upstream, issue_number) + log.debug( + "Issue %s#%s is not associated with any project", upstream, issue_number + ) return None if not project_number: @@ -384,32 +404,37 @@ def _get_current_project_node(upstream, project_number, issue_number, gh_issue): log.debug( "Project number is not configured, and the issue %s#%s" " is associated with more than one project: %s", - upstream, issue_number, ", ".join(prj)) + upstream, + issue_number, + ", ".join(prj), + ) return None # Return the associated project which matches the configured project if we # can find it. for item in project_items: - if item['project']['number'] == project_number: + if item["project"]["number"] == project_number: return item log.debug( "Issue %s#%s is associated with multiple projects, " "but none match the configured project.", - upstream, issue_number) + upstream, + issue_number, + ) return None def get_all_github_data(url, headers): """A generator which returns each response from a paginated GitHub API call""" - link = {'next': url} - while 'next' in link: - response = api_call_get(link['next'], headers=headers) + link = {"next": url} + while "next" in link: + response = api_call_get(link["next"], headers=headers) for issue in response.json(): - comments = api_call_get(issue['comments_url'], headers=headers) - issue['comments'] = comments.json() + comments = api_call_get(issue["comments_url"], headers=headers) + issue["comments"] = comments.json() yield issue - link = _github_link_field_to_dict(response.headers.get('link')) + link = _github_link_field_to_dict(response.headers.get("link")) def _github_link_field_to_dict(field): @@ -418,10 +443,8 @@ def _github_link_field_to_dict(field): if not field: return {} return dict( - ( - part.split('; ')[1][5:-1], - part.split('; ')[0][1:-1] - ) for part in field.split(', ') + (part.split("; ")[1][5:-1], part.split("; ")[0][1:-1]) + for part in field.split(", ") ) diff --git a/sync2jira/upstream_pr.py b/sync2jira/upstream_pr.py index a9528523..d161a796 100644 --- a/sync2jira/upstream_pr.py +++ b/sync2jira/upstream_pr.py @@ -24,7 +24,7 @@ import sync2jira.intermediary as i import sync2jira.upstream_issue as u_issue -log = logging.getLogger('sync2jira') +log = logging.getLogger("sync2jira") def handle_github_message(msg, config, suffix): @@ -37,20 +37,20 @@ def handle_github_message(msg, config, suffix): :returns: Issue object :rtype: sync2jira.intermediary.PR """ - owner = msg['msg']['repository']['owner']['login'] - repo = msg['msg']['repository']['name'] - upstream = '{owner}/{repo}'.format(owner=owner, repo=repo) + owner = msg["msg"]["repository"]["owner"]["login"] + repo = msg["msg"]["repository"]["name"] + upstream = "{owner}/{repo}".format(owner=owner, repo=repo) - mapped_repos = config['sync2jira']['map']['github'] + mapped_repos = config["sync2jira"]["map"]["github"] if upstream not in mapped_repos: log.debug("%r not in Github map: %r", upstream, mapped_repos.keys()) return None - elif 'pullrequest' not in mapped_repos[upstream]['sync']: + elif "pullrequest" not in mapped_repos[upstream]["sync"]: log.debug("%r not in Github PR map: %r", upstream, mapped_repos.keys()) return None - pr = msg['msg']['pull_request'] - github_client = Github(config['sync2jira']['github_token']) + pr = msg["msg"]["pull_request"] + github_client = Github(config["sync2jira"]["github_token"]) reformat_github_pr(pr, upstream, github_client) return i.PR.from_github(upstream, pr, suffix, config) @@ -64,10 +64,10 @@ def github_prs(upstream, config): :returns: a generator for GitHub PR objects :rtype: Generator[sync2jira.intermediary.PR] """ - github_client = Github(config['sync2jira']['github_token']) - for pr in u_issue.generate_github_items('pulls', upstream, config): + github_client = Github(config["sync2jira"]["github_token"]) + for pr in u_issue.generate_github_items("pulls", upstream, config): reformat_github_pr(pr, upstream, github_client) - yield i.PR.from_github(upstream, pr, 'open', config) + yield i.PR.from_github(upstream, pr, "open", config) def reformat_github_pr(pr, upstream, github_client): @@ -75,12 +75,14 @@ def reformat_github_pr(pr, upstream, github_client): # Update comments: # If there are no comments just make an empty array - if not pr['comments']: - pr['comments'] = [] + if not pr["comments"]: + pr["comments"] = [] else: # We have multiple comments and need to make api call to get them repo = github_client.get_repo(upstream) - github_pr = repo.get_pull(number=pr['number']) - pr['comments'] = u_issue.reformat_github_comments(github_pr.get_issue_comments()) + github_pr = repo.get_pull(number=pr["number"]) + pr["comments"] = u_issue.reformat_github_comments( + github_pr.get_issue_comments() + ) u_issue.reformat_github_common(pr, github_client) diff --git a/tests/integration_tests/integration_test.py b/tests/integration_tests/integration_test.py index 8c37279b..3b8fd08e 100644 --- a/tests/integration_tests/integration_test.py +++ b/tests/integration_tests/integration_test.py @@ -1,6 +1,7 @@ """ This is a helper program to listen for UMB trigger. Test and then deploy Sync2Jira """ + # Built-In Modules import logging import os @@ -15,11 +16,11 @@ from sync2jira.main import main as m # Global Variables -URL = os.environ['JIRA_STAGE_URL'] -TOKEN = os.environ['JIRA_TOKEN'] +URL = os.environ["JIRA_STAGE_URL"] +TOKEN = os.environ["JIRA_TOKEN"] log = logging.getLogger(__name__) -hdlr = logging.FileHandler('integration_test.log') -formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') +hdlr = logging.FileHandler("integration_test.log") +formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") hdlr.setFormatter(formatter) log.addHandler(hdlr) log.setLevel(logging.DEBUG) @@ -46,7 +47,9 @@ def main(): compare_data(client, GITHUB) except Exception as e: failed = True - log.info(f"[OS-BUILD] When comparing GitHub something went wrong.\nException {e}") + log.info( + f"[OS-BUILD] When comparing GitHub something went wrong.\nException {e}" + ) if failed: log.info("[OS-BUILD] Tests have failed :(") @@ -63,7 +66,7 @@ def compare_data(client, data): :return: True/False if we """ # First get our existing JIRA issue - jira_ticket = data['JIRA'] + jira_ticket = data["JIRA"] existing = client.search_issues(f"Key = {jira_ticket}") # Throw an error if too many issues were found @@ -71,43 +74,59 @@ def compare_data(client, data): raise Exception(f"Too many issues were found with ticket {jira_ticket}") existing = existing[0] - log.info("TEST - "+existing.fields.summary) + log.info("TEST - " + existing.fields.summary) # Check Tags - if data['tags'] != existing.fields.labels: - raise Exception(f"Error when comparing tags for {jira_ticket}\n" - f"Expected: {data['tags']}\n" - f"Actual: {existing.fields.labels}") + if data["tags"] != existing.fields.labels: + raise Exception( + f"Error when comparing tags for {jira_ticket}\n" + f"Expected: {data['tags']}\n" + f"Actual: {existing.fields.labels}" + ) # Check FixVersion formatted_fixVersion = format_fixVersion(existing.fields.fixVersions) - if data['fixVersions'] != formatted_fixVersion: - raise Exception(f"Error when comparing fixVersions for {jira_ticket}\n" - f"Expected: {data['fixVersions']}\n" - f"Actual: {formatted_fixVersion}") + if data["fixVersions"] != formatted_fixVersion: + raise Exception( + f"Error when comparing fixVersions for {jira_ticket}\n" + f"Expected: {data['fixVersions']}\n" + f"Actual: {formatted_fixVersion}" + ) # Check Assignee if not existing.fields.assignee: - raise Exception(f"Error when comparing assignee for {jira_ticket}\n" - f"Expected: {data['assignee']}\n" - f"Actual: {existing.fields.assignee}") - - elif data['assignee'] != existing.fields.assignee.name: - raise Exception(f"Error when comparing assignee for {jira_ticket}\n" - f"Expected: {data['assignee']}\n" - f"Actual: {existing.fields.assignee.name}") + raise Exception( + f"Error when comparing assignee for {jira_ticket}\n" + f"Expected: {data['assignee']}\n" + f"Actual: {existing.fields.assignee}" + ) + + elif data["assignee"] != existing.fields.assignee.name: + raise Exception( + f"Error when comparing assignee for {jira_ticket}\n" + f"Expected: {data['assignee']}\n" + f"Actual: {existing.fields.assignee.name}" + ) # Check Title - if data['title'] != existing.fields.summary: - raise Exception(f"Error when comparing title for {jira_ticket}\n" - f"Expected: {data['title']}\n" - f"Actual: {existing.fields.summary}") + if data["title"] != existing.fields.summary: + raise Exception( + f"Error when comparing title for {jira_ticket}\n" + f"Expected: {data['title']}\n" + f"Actual: {existing.fields.summary}" + ) # Check Descriptions - if data['description'].replace("\n", "").replace(" ", "").replace("\r", "") != existing.fields.description.replace("\n", "").replace(" ", "").replace("\r", ""): - raise Exception(f"Error when comparing descriptions for {jira_ticket}\n" - f"Expected: {data['description']}\n" - f"Actual: {existing.fields.description}") + if data["description"].replace("\n", "").replace(" ", "").replace( + "\r", "" + ) != existing.fields.description.replace("\n", "").replace(" ", "").replace( + "\r", "" + ): + raise Exception( + f"Error when comparing descriptions for {jira_ticket}\n" + f"Expected: {data['description']}\n" + f"Actual: {existing.fields.description}" + ) def format_fixVersion(existing): @@ -129,15 +148,17 @@ def get_jira_client(): :return: JIRA Client :rtype: jira.client.JIRA """ - return jira.client.JIRA(**{ - 'options': { - 'server': URL, - 'verify': False, - }, - 'token_auth': TOKEN, - }) - - -if __name__ == '__main__': + return jira.client.JIRA( + **{ + "options": { + "server": URL, + "verify": False, + }, + "token_auth": TOKEN, + } + ) + + +if __name__ == "__main__": # Call our main method after parsing out message main() diff --git a/tests/integration_tests/jira_values.py b/tests/integration_tests/jira_values.py index 376e9114..36b0e5fc 100644 --- a/tests/integration_tests/jira_values.py +++ b/tests/integration_tests/jira_values.py @@ -1,8 +1,8 @@ GITHUB = { - 'JIRA': 'FACTORY-6186', - 'title': '[sidpremkumar/Demo_repo] Test Issue DO NOT TOUCH', - 'description': '[555670302] Upstream Reporter: Sid Premkumar \n Upstream issue status: Open\nUpstream description: {quote}Some Description{quote} \nUpstream URL: https://github.com/sidpremkumar/Demo_repo/issues/30', - 'fixVersions': ['FY19 Q1'], - 'assignee': 'sid', - 'tags': ['bug'], + "JIRA": "FACTORY-6186", + "title": "[sidpremkumar/Demo_repo] Test Issue DO NOT TOUCH", + "description": "[555670302] Upstream Reporter: Sid Premkumar \n Upstream issue status: Open\nUpstream description: {quote}Some Description{quote} \nUpstream URL: https://github.com/sidpremkumar/Demo_repo/issues/30", + "fixVersions": ["FY19 Q1"], + "assignee": "sid", + "tags": ["bug"], } diff --git a/tests/integration_tests/runtime_config.py b/tests/integration_tests/runtime_config.py index 44ce9c36..52db84ec 100644 --- a/tests/integration_tests/runtime_config.py +++ b/tests/integration_tests/runtime_config.py @@ -1,46 +1,46 @@ import os runtime_config = { - 'sync2jira': { - 'jira': { - 'pnt-jira': { - 'options': { - 'server': os.environ['JIRA_STAGE_URL'], - 'verify': True, + "sync2jira": { + "jira": { + "pnt-jira": { + "options": { + "server": os.environ["JIRA_STAGE_URL"], + "verify": True, }, - 'token_auth': os.environ['JIRA_TOKEN'], + "token_auth": os.environ["JIRA_TOKEN"], }, }, - 'github_token': os.environ['SYNC2JIRA_GITHUB_TOKEN'], - 'admins': [{'spremkum', 'spremkum@redhat.com'}, {'rbean', 'rbean@redhat.com'}], - 'initialize': True, - 'testing': False, - 'develop': True, - + "github_token": os.environ["SYNC2JIRA_GITHUB_TOKEN"], + "admins": [{"spremkum", "spremkum@redhat.com"}, {"rbean", "rbean@redhat.com"}], + "initialize": True, + "testing": False, + "develop": True, # We don't need legacy mode anymore. Not for a long time. Let's # remove it soon. - 'legacy_matching': False, - + "legacy_matching": False, # Set the default jira to be pnt-jira - 'default_jira_instance': 'pnt-jira', - - 'filters': { - 'github': { - }, + "default_jira_instance": "pnt-jira", + "filters": { + "github": {}, }, - 'map': { - 'github': { - 'sidpremkumar/Demo_repo': {'project': 'FACTORY', 'component': 'gitbz', - 'issue_updates': [{'transition': True}, - 'description', - 'title', - {'tags': {'overwrite': True}}, - {'fixVersion': {'overwrite': True}}, - {'assignee': {'overwrite': True}}, - 'url'], - 'sync': ['issue']} - + "map": { + "github": { + "sidpremkumar/Demo_repo": { + "project": "FACTORY", + "component": "gitbz", + "issue_updates": [ + {"transition": True}, + "description", + "title", + {"tags": {"overwrite": True}}, + {"fixVersion": {"overwrite": True}}, + {"assignee": {"overwrite": True}}, + "url", + ], + "sync": ["issue"], + } }, }, - } + } } diff --git a/tests/test_downstream_issue.py b/tests/test_downstream_issue.py index f273bfd9..b0bbac3f 100644 --- a/tests/test_downstream_issue.py +++ b/tests/test_downstream_issue.py @@ -9,120 +9,119 @@ import sync2jira.downstream_issue as d from sync2jira.intermediary import Issue -PATH = 'sync2jira.downstream_issue.' +PATH = "sync2jira.downstream_issue." class TestDownstreamIssue(unittest.TestCase): """ This class tests the downstream_issue.py file under sync2jira """ + def setUp(self): """ Setting up the testing environment """ # Mock Config dict self.mock_config = { - 'sync2jira': { - 'default_jira_instance': 'another_jira_instance', - 'jira_username': 'mock_user', - 'default_jira_fields': {'storypoints': 'customfield_12310243'}, - 'jira': { - 'mock_jira_instance': {'mock_jira': 'mock_jira'}, - 'another_jira_instance': {'token_auth': 'mock_token', - 'options': {'server': 'mock_server'}} + "sync2jira": { + "default_jira_instance": "another_jira_instance", + "jira_username": "mock_user", + "default_jira_fields": {"storypoints": "customfield_12310243"}, + "jira": { + "mock_jira_instance": {"mock_jira": "mock_jira"}, + "another_jira_instance": { + "token_auth": "mock_token", + "options": {"server": "mock_server"}, + }, }, - 'testing': {}, - 'legacy_matching': False, - 'admins': [{'mock_admin': 'mock_email'}], - 'develop': False + "testing": {}, + "legacy_matching": False, + "admins": [{"mock_admin": "mock_email"}], + "develop": False, }, } # Mock sync2jira.intermediary.Issue self.mock_issue = MagicMock() - self.mock_issue.assignee = [{'fullname': 'mock_user'}] + self.mock_issue.assignee = [{"fullname": "mock_user"}] self.mock_issue.downstream = { - 'project': 'mock_project', - 'custom_fields': {'somecustumfield': 'somecustumvalue'}, - 'qa-contact': 'dummy@dummy.com', - 'epic-link': 'DUMMY-1234', - 'EXD-Service': {'guild': 'EXD-Project', 'value': 'EXD-Value'}, - 'issue_updates': [ - 'comments', - {'tags': {'overwrite': False}}, - {'fixVersion': {'overwrite': False}}, - {'assignee': {'overwrite': True}}, 'description', 'title', - {'transition': 'CUSTOM TRANSITION'}, - {'on_close': {"apply_labels": ["closed-upstream"]}} + "project": "mock_project", + "custom_fields": {"somecustumfield": "somecustumvalue"}, + "qa-contact": "dummy@dummy.com", + "epic-link": "DUMMY-1234", + "EXD-Service": {"guild": "EXD-Project", "value": "EXD-Value"}, + "issue_updates": [ + "comments", + {"tags": {"overwrite": False}}, + {"fixVersion": {"overwrite": False}}, + {"assignee": {"overwrite": True}}, + "description", + "title", + {"transition": "CUSTOM TRANSITION"}, + {"on_close": {"apply_labels": ["closed-upstream"]}}, ], - 'owner': 'mock_owner' + "owner": "mock_owner", } - self.mock_issue.content = 'mock_content' - self.mock_issue.reporter = {'fullname': 'mock_user'} - self.mock_issue.url = 'mock_url' - self.mock_issue.title = 'mock_title' - self.mock_issue.comments = 'mock_comments' - self.mock_issue.tags = ['tag1', 'tag2'] - self.mock_issue.fixVersion = ['fixVersion3', 'fixVersion4'] - self.mock_issue.fixVersion = ['fixVersion3', 'fixVersion4'] - self.mock_issue.assignee = [{'fullname': 'mock_assignee'}] - self.mock_issue.status = 'Open' - self.mock_issue.id = '1234' + self.mock_issue.content = "mock_content" + self.mock_issue.reporter = {"fullname": "mock_user"} + self.mock_issue.url = "mock_url" + self.mock_issue.title = "mock_title" + self.mock_issue.comments = "mock_comments" + self.mock_issue.tags = ["tag1", "tag2"] + self.mock_issue.fixVersion = ["fixVersion3", "fixVersion4"] + self.mock_issue.fixVersion = ["fixVersion3", "fixVersion4"] + self.mock_issue.assignee = [{"fullname": "mock_assignee"}] + self.mock_issue.status = "Open" + self.mock_issue.id = "1234" self.mock_issue.storypoints = 2 - self.mock_issue.priority = 'P1' + self.mock_issue.priority = "P1" # Mock issue updates self.mock_updates = [ - 'comments', - {'tags': {'overwrite': False}}, - {'fixVersion': {'overwrite': False}}, - {'assignee': {'overwrite': True}}, 'description', 'title', - {'transition': 'CUSTOM TRANSITION'}, - {'on_close': {"apply_labels": ["closed-upstream"]}} - ] + "comments", + {"tags": {"overwrite": False}}, + {"fixVersion": {"overwrite": False}}, + {"assignee": {"overwrite": True}}, + "description", + "title", + {"transition": "CUSTOM TRANSITION"}, + {"on_close": {"apply_labels": ["closed-upstream"]}}, + ] # Mock Jira transition - self.mock_transition = [{ - 'name': 'custom_closed_status', - 'id': 1234 - }] + self.mock_transition = [{"name": "custom_closed_status", "id": 1234}] # Mock jira.resources.Issue self.mock_downstream = MagicMock() self.mock_downstream.id = 1234 - self.mock_downstream.fields.labels = ['tag3', 'tag4'] + self.mock_downstream.fields.labels = ["tag3", "tag4"] mock_version1 = MagicMock() - mock_version1.name = 'fixVersion3' + mock_version1.name = "fixVersion3" mock_version2 = MagicMock() - mock_version2.name = 'fixVersion4' + mock_version2.name = "fixVersion4" self.mock_downstream.fields.fixVersions = [mock_version1, mock_version2] self.mock_downstream.update.return_value = True self.mock_downstream.fields.description = "This is an existing description" # Mock datetime.today() self.mock_today = MagicMock() - self.mock_today.strftime.return_value = 'mock_today' + self.mock_today.strftime.return_value = "mock_today" - @mock.patch('jira.client.JIRA') - def test_get_jira_client_not_issue(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_get_jira_client_not_issue(self, mock_client): """ This tests 'get_jira_client' function where the passed in argument is not an Issue instance """ # Call the function with self.assertRaises(Exception): - d.get_jira_client( - issue='string', - config=self.mock_config - ) + d.get_jira_client(issue="string", config=self.mock_config) # Assert everything was called correctly mock_client.assert_not_called() - @mock.patch('jira.client.JIRA') - def test_get_jira_client_not_instance(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_get_jira_client_not_instance(self, mock_client): """ This tests 'get_jira_client' function there is no JIRA instance """ @@ -131,44 +130,39 @@ def test_get_jira_client_not_instance(self, # Call the function with self.assertRaises(Exception): - d.get_jira_client( - issue=self.mock_issue, - config=self.mock_config - ) + d.get_jira_client(issue=self.mock_issue, config=self.mock_config) # Assert everything was called correctly mock_client.assert_not_called() - @mock.patch('jira.client.JIRA') - def test_get_jira_client(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_get_jira_client(self, mock_client): """ This tests 'get_jira_client' function where everything goes smoothly """ # Set up return values mock_issue = MagicMock(spec=Issue) - mock_issue.downstream = {'jira_instance': 'mock_jira_instance'} - mock_client.return_value = 'Successful call!' + mock_issue.downstream = {"jira_instance": "mock_jira_instance"} + mock_client.return_value = "Successful call!" # Call the function - response = d.get_jira_client( - issue=mock_issue, - config=self.mock_config - ) + response = d.get_jira_client(issue=mock_issue, config=self.mock_config) # Assert everything was called correctly - mock_client.assert_called_with(mock_jira='mock_jira') - self.assertEqual('Successful call!', response) + mock_client.assert_called_with(mock_jira="mock_jira") + self.assertEqual("Successful call!", response) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_get_existing_legacy(self, client): """ This tests '_get_existing_jira_issue_legacy' function """ + class MockIssue(object): - downstream = {'key': 'value'} - url = 'wat' + downstream = {"key": "value"} + url = "wat" + issue = MockIssue() config = self.mock_config # Ensure that we get results back from the jira client. @@ -182,19 +176,18 @@ class MockIssue(object): "(resolution is null OR resolution = Duplicate)", ) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_get_existing_newstyle(self, client): config = self.mock_config class MockIssue(object): - downstream = {'key': 'value'} - title = 'A title, a title...' - url = 'http://threebean.org' - + downstream = {"key": "value"} + title = "A title, a title..." + url = "http://threebean.org" issue = MockIssue() mock_results_of_query = MagicMock() - mock_results_of_query.fields.summary = 'A title, a title...' + mock_results_of_query.fields.summary = "A title, a title..." client.return_value.search_issues.return_value = [mock_results_of_query] result = d._get_existing_jira_issue(jira.client.JIRA(), issue, config) @@ -206,14 +199,14 @@ class MockIssue(object): 'issueFunction in linkedIssuesOfRemote("http://threebean.org")' ) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_upgrade_oldstyle_jira_issue(self, client): config = self.mock_config class MockIssue(object): - downstream = {'key': 'value'} - title = 'A title, a title...' - url = 'http://threebean.org' + downstream = {"key": "value"} + title = "A title, a title..." + url = "http://threebean.org" downstream = mock.MagicMock() issue = MockIssue() @@ -222,92 +215,82 @@ class MockIssue(object): d._upgrade_jira_issue(jira.client.JIRA(), downstream, issue, config) remote = { - 'url': 'http://threebean.org', - 'title': 'Upstream issue', + "url": "http://threebean.org", + "title": "Upstream issue", } client_obj.add_remote_link.assert_called_once_with(downstream.id, remote) - - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_assign_user(self, mock_client): """ Test 'assign_user' function where remove_all flag is False """ # Set up return values mock_user = MagicMock() - mock_user.displayName = 'mock_assignee' - mock_user.key = 'mock_user_key' + mock_user.displayName = "mock_assignee" + mock_user.key = "mock_user_key" mock_client.search_assignable_users_for_issues.return_value = [mock_user] mock_client.assign_issue.return_value = True # Call the assign user function d.assign_user( - issue=self.mock_issue, - downstream=self.mock_downstream, - client=mock_client + issue=self.mock_issue, downstream=self.mock_downstream, client=mock_client ) # Assert that all calls mocked were called properly - self.mock_downstream.update({'assignee': {'name': 1234}}) + self.mock_downstream.update({"assignee": {"name": 1234}}) mock_client.search_assignable_users_for_issues.assert_called_with( - 'mock_assignee', - project='mock_project' + "mock_assignee", project="mock_project" ) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_assign_user_with_owner(self, mock_client): """ Test 'assign_user' function where remove_all flag is False """ # Set up return values mock_user = MagicMock() - mock_user.displayName = 'mock_assignee' - mock_user.key = 'mock_user_key' + mock_user.displayName = "mock_assignee" + mock_user.key = "mock_user_key" mock_client.search_assignable_users_for_issues.return_value = [] mock_client.assign_issue.return_value = True # Call the assign user function d.assign_user( - issue=self.mock_issue, - downstream=self.mock_downstream, - client=mock_client + issue=self.mock_issue, downstream=self.mock_downstream, client=mock_client ) # Assert that all calls mocked were called properly - mock_client.assign_issue.assert_called_with(1234, 'mock_owner') + mock_client.assign_issue.assert_called_with(1234, "mock_owner") mock_client.search_assignable_users_for_issues.assert_called_with( - 'mock_assignee', - project='mock_project' + "mock_assignee", project="mock_project" ) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_assign_user_without_owner(self, mock_client): """ Test 'assign_user' function where remove_all flag is False """ # Set up return values mock_user = MagicMock() - mock_user.displayName = 'mock_assignee' - mock_user.key = 'mock_user_key' + mock_user.displayName = "mock_assignee" + mock_user.key = "mock_user_key" mock_client.search_assignable_users_for_issues.return_value = [] mock_client.assign_issue.return_value = True - self.mock_issue.downstream.pop('owner') + self.mock_issue.downstream.pop("owner") # Call the assign user function d.assign_user( - issue=self.mock_issue, - downstream=self.mock_downstream, - client=mock_client + issue=self.mock_issue, downstream=self.mock_downstream, client=mock_client ) # Assert that all calls mocked were called properly mock_client.assign_issue.assert_not_called() mock_client.search_assignable_users_for_issues.assert_called_with( - 'mock_assignee', - project='mock_project' + "mock_assignee", project="mock_project" ) - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_assign_user_remove_all(self, mock_client): """ Test 'assign_user' function where remove_all flag is True @@ -317,246 +300,222 @@ def test_assign_user_remove_all(self, mock_client): issue=self.mock_issue, downstream=self.mock_downstream, client=mock_client, - remove_all=True + remove_all=True, ) # Assert that all calls mocked were called properly - self.mock_downstream.update.assert_called_with(assignee={'name': ''}) + self.mock_downstream.update.assert_called_with(assignee={"name": ""}) mock_client.assign_issue.assert_not_called() mock_client.search_assignable_users_for_issues.assert_not_called() - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + 'attach_link') - @mock.patch('jira.client.JIRA') - def test_create_jira_issue(self, - mock_client, - mock_attach_link, - mock_update_jira_issue): + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "attach_link") + @mock.patch("jira.client.JIRA") + def test_create_jira_issue( + self, mock_client, mock_attach_link, mock_update_jira_issue + ): """ Tests '_create_jira_issue' function """ # Set up return values mock_client.create_issue.return_value = self.mock_downstream mock_client.fields.return_value = [ - {'name': 'Epic Link', 'id': 'customfield_1'}, - {'name': 'QA Contact', 'id': 'customfield_2'}, - {'name': 'EXD-Service', 'id': 'customfield_3'}, + {"name": "Epic Link", "id": "customfield_1"}, + {"name": "QA Contact", "id": "customfield_2"}, + {"name": "EXD-Service", "id": "customfield_3"}, ] # Call the function response = d._create_jira_issue( - client=mock_client, - issue=self.mock_issue, - config=self.mock_config + client=mock_client, issue=self.mock_issue, config=self.mock_config ) # Assert everything was called correctly mock_client.create_issue.assert_called_with( - issuetype={'name': 'Bug'}, - project={'key': 'mock_project'}, - somecustumfield='somecustumvalue', - description='[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}', - summary='mock_title' + issuetype={"name": "Bug"}, + project={"key": "mock_project"}, + somecustumfield="somecustumvalue", + description="[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}", + summary="mock_title", ) mock_attach_link.assert_called_with( mock_client, self.mock_downstream, - { - 'url': 'mock_url', - 'title': 'Upstream issue' - } + {"url": "mock_url", "title": "Upstream issue"}, ) mock_update_jira_issue.assert_called_with( - self.mock_downstream, - self.mock_issue, - mock_client, - self.mock_config + self.mock_downstream, self.mock_issue, mock_client, self.mock_config + ) + self.mock_downstream.update.assert_any_call({"customfield_1": "DUMMY-1234"}) + self.mock_downstream.update.assert_any_call( + {"customfield_2": "dummy@dummy.com"} ) - self.mock_downstream.update.assert_any_call({'customfield_1': 'DUMMY-1234'}) - self.mock_downstream.update.assert_any_call({'customfield_2': 'dummy@dummy.com'}) self.mock_downstream.update.assert_any_call( - {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}}) + {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}} + ) self.assertEqual(response, self.mock_downstream) mock_client.add_comment.assert_not_called() - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + 'attach_link') - @mock.patch('jira.client.JIRA') - def test_create_jira_issue_failed_epic_link(self, - mock_client, - mock_attach_link, - mock_update_jira_issue): + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "attach_link") + @mock.patch("jira.client.JIRA") + def test_create_jira_issue_failed_epic_link( + self, mock_client, mock_attach_link, mock_update_jira_issue + ): """ Tests '_create_jira_issue' function where we fail updating the epic link """ # Set up return values mock_client.create_issue.return_value = self.mock_downstream mock_client.fields.return_value = [ - {'name': 'Epic Link', 'id': 'customfield_1'}, - {'name': 'QA Contact', 'id': 'customfield_2'}, - {'name': 'EXD-Service', 'id': 'customfield_3'}, + {"name": "Epic Link", "id": "customfield_1"}, + {"name": "QA Contact", "id": "customfield_2"}, + {"name": "EXD-Service", "id": "customfield_3"}, ] - self.mock_downstream.update.side_effect = [JIRAError, 'success', 'success'] + self.mock_downstream.update.side_effect = [JIRAError, "success", "success"] # Call the function response = d._create_jira_issue( - client=mock_client, - issue=self.mock_issue, - config=self.mock_config + client=mock_client, issue=self.mock_issue, config=self.mock_config ) # Assert everything was called correctly mock_client.create_issue.assert_called_with( - issuetype={'name': 'Bug'}, - project={'key': 'mock_project'}, - somecustumfield='somecustumvalue', - description='[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}', - summary='mock_title' + issuetype={"name": "Bug"}, + project={"key": "mock_project"}, + somecustumfield="somecustumvalue", + description="[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}", + summary="mock_title", ) mock_attach_link.assert_called_with( mock_client, self.mock_downstream, - { - 'url': 'mock_url', - 'title': 'Upstream issue' - } + {"url": "mock_url", "title": "Upstream issue"}, ) mock_update_jira_issue.assert_called_with( - self.mock_downstream, - self.mock_issue, - mock_client, - self.mock_config + self.mock_downstream, self.mock_issue, mock_client, self.mock_config ) - self.mock_downstream.update.assert_any_call({'customfield_1': 'DUMMY-1234'}) + self.mock_downstream.update.assert_any_call({"customfield_1": "DUMMY-1234"}) self.mock_downstream.update.assert_any_call( - {'customfield_2': 'dummy@dummy.com'}) + {"customfield_2": "dummy@dummy.com"} + ) self.mock_downstream.update.assert_any_call( - {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}}) + {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}} + ) self.assertEqual(response, self.mock_downstream) - mock_client.add_comment.assert_called_with(self.mock_downstream, f"Error adding Epic-Link: DUMMY-1234") + mock_client.add_comment.assert_called_with( + self.mock_downstream, f"Error adding Epic-Link: DUMMY-1234" + ) - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + 'attach_link') - @mock.patch('jira.client.JIRA') - def test_create_jira_issue_failed_exd_service(self, - mock_client, - mock_attach_link, - mock_update_jira_issue): + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "attach_link") + @mock.patch("jira.client.JIRA") + def test_create_jira_issue_failed_exd_service( + self, mock_client, mock_attach_link, mock_update_jira_issue + ): """ Tests '_create_jira_issue' function where we fail updating the EXD-Service field """ # Set up return values mock_client.create_issue.return_value = self.mock_downstream mock_client.fields.return_value = [ - {'name': 'Epic Link', 'id': 'customfield_1'}, - {'name': 'QA Contact', 'id': 'customfield_2'}, - {'name': 'EXD-Service', 'id': 'customfield_3'}, + {"name": "Epic Link", "id": "customfield_1"}, + {"name": "QA Contact", "id": "customfield_2"}, + {"name": "EXD-Service", "id": "customfield_3"}, ] - self.mock_downstream.update.side_effect = ['success', 'success', JIRAError] + self.mock_downstream.update.side_effect = ["success", "success", JIRAError] # Call the function response = d._create_jira_issue( - client=mock_client, - issue=self.mock_issue, - config=self.mock_config + client=mock_client, issue=self.mock_issue, config=self.mock_config ) # Assert everything was called correctly mock_client.create_issue.assert_called_with( - issuetype={'name': 'Bug'}, - project={'key': 'mock_project'}, - somecustumfield='somecustumvalue', - description='[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}', - summary='mock_title' + issuetype={"name": "Bug"}, + project={"key": "mock_project"}, + somecustumfield="somecustumvalue", + description="[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}", + summary="mock_title", ) mock_attach_link.assert_called_with( mock_client, self.mock_downstream, - { - 'url': 'mock_url', - 'title': 'Upstream issue' - } + {"url": "mock_url", "title": "Upstream issue"}, ) mock_update_jira_issue.assert_called_with( - self.mock_downstream, - self.mock_issue, - mock_client, - self.mock_config + self.mock_downstream, self.mock_issue, mock_client, self.mock_config ) - self.mock_downstream.update.assert_any_call({'customfield_1': 'DUMMY-1234'}) + self.mock_downstream.update.assert_any_call({"customfield_1": "DUMMY-1234"}) self.mock_downstream.update.assert_any_call( - {'customfield_2': 'dummy@dummy.com'}) + {"customfield_2": "dummy@dummy.com"} + ) self.mock_downstream.update.assert_any_call( - {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}}) + {"customfield_3": {"value": "EXD-Project", "child": {"value": "EXD-Value"}}} + ) self.assertEqual(response, self.mock_downstream) - mock_client.add_comment.assert_called_with(self.mock_downstream, - f"Error adding EXD-Service field.\n" - f"Project: {self.mock_issue.downstream['EXD-Service']['guild']}\n" - f"Value: {self.mock_issue.downstream['EXD-Service']['value']}") - - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + 'attach_link') - @mock.patch('jira.client.JIRA') - def test_create_jira_issue_no_updates(self, - mock_client, - mock_attach_link, - mock_update_jira_issue): + mock_client.add_comment.assert_called_with( + self.mock_downstream, + f"Error adding EXD-Service field.\n" + f"Project: {self.mock_issue.downstream['EXD-Service']['guild']}\n" + f"Value: {self.mock_issue.downstream['EXD-Service']['value']}", + ) + + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "attach_link") + @mock.patch("jira.client.JIRA") + def test_create_jira_issue_no_updates( + self, mock_client, mock_attach_link, mock_update_jira_issue + ): """ Tests '_create_jira_issue' function where we have no updates """ # Set up return values mock_client.create_issue.return_value = self.mock_downstream - self.mock_issue.downstream['issue_updates'] = [] + self.mock_issue.downstream["issue_updates"] = [] # Call the function response = d._create_jira_issue( - client=mock_client, - issue=self.mock_issue, - config=self.mock_config + client=mock_client, issue=self.mock_issue, config=self.mock_config ) # Assert everything was called correctly mock_client.create_issue.assert_called_with( - issuetype={'name': 'Bug'}, - project={'key': 'mock_project'}, - somecustumfield='somecustumvalue', - description='[1234] Upstream Reporter: mock_user\n', - summary='mock_title' + issuetype={"name": "Bug"}, + project={"key": "mock_project"}, + somecustumfield="somecustumvalue", + description="[1234] Upstream Reporter: mock_user\n", + summary="mock_title", ) mock_attach_link.assert_called_with( mock_client, self.mock_downstream, - { - 'url': 'mock_url', - 'title': 'Upstream issue' - } + {"url": "mock_url", "title": "Upstream issue"}, ) mock_update_jira_issue.assert_called_with( - self.mock_downstream, - self.mock_issue, - mock_client, - self.mock_config + self.mock_downstream, self.mock_issue, mock_client, self.mock_config ) self.assertEqual(response, self.mock_downstream) mock_client.add_comment.assert_not_called() - - @mock.patch(PATH + 'get_jira_client') - @mock.patch(PATH + '_get_existing_jira_issue') - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + '_create_jira_issue') - @mock.patch('jira.client.JIRA') - @mock.patch(PATH + '_get_existing_jira_issue_legacy') - @mock.patch(PATH + 'check_jira_status') - def test_sync_with_jira_matching(self, - mock_check_jira_status, - mock_existing_jira_issue_legacy, - mock_client, - mock_create_jira_issue, - mock_update_jira_issue, - mock_existing_jira_issue, - mock_get_jira_client): + @mock.patch(PATH + "get_jira_client") + @mock.patch(PATH + "_get_existing_jira_issue") + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "_create_jira_issue") + @mock.patch("jira.client.JIRA") + @mock.patch(PATH + "_get_existing_jira_issue_legacy") + @mock.patch(PATH + "check_jira_status") + def test_sync_with_jira_matching( + self, + mock_check_jira_status, + mock_existing_jira_issue_legacy, + mock_client, + mock_create_jira_issue, + mock_update_jira_issue, + mock_existing_jira_issue, + mock_get_jira_client, + ): """ Tests 'sync_with_jira' function where we do find a matching issue This assumes we're not using the legacy matching anymore @@ -567,33 +526,33 @@ def test_sync_with_jira_matching(self, mock_check_jira_status.return_value = True # Call the function - d.sync_with_jira( - issue=self.mock_issue, - config=self.mock_config - ) + d.sync_with_jira(issue=self.mock_issue, config=self.mock_config) # Assert all calls were made correctly mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config) - mock_update_jira_issue.assert_called_with(self.mock_downstream, self.mock_issue, - mock_client, self.mock_config) + mock_update_jira_issue.assert_called_with( + self.mock_downstream, self.mock_issue, mock_client, self.mock_config + ) mock_create_jira_issue.assert_not_called() mock_existing_jira_issue_legacy.assert_not_called() - @mock.patch(PATH + 'get_jira_client') - @mock.patch(PATH + '_get_existing_jira_issue') - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + '_create_jira_issue') - @mock.patch('jira.client.JIRA') - @mock.patch(PATH + '_get_existing_jira_issue_legacy') - @mock.patch(PATH + 'check_jira_status') - def test_sync_with_jira_down(self, - mock_check_jira_status, - mock_existing_jira_issue_legacy, - mock_client, - mock_create_jira_issue, - mock_update_jira_issue, - mock_existing_jira_issue, - mock_get_jira_client): + @mock.patch(PATH + "get_jira_client") + @mock.patch(PATH + "_get_existing_jira_issue") + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "_create_jira_issue") + @mock.patch("jira.client.JIRA") + @mock.patch(PATH + "_get_existing_jira_issue_legacy") + @mock.patch(PATH + "check_jira_status") + def test_sync_with_jira_down( + self, + mock_check_jira_status, + mock_existing_jira_issue_legacy, + mock_client, + mock_create_jira_issue, + mock_update_jira_issue, + mock_existing_jira_issue, + mock_get_jira_client, + ): """ Tests 'sync_with_jira' function where the JIRA scriptrunner is down """ @@ -604,10 +563,7 @@ def test_sync_with_jira_down(self, # Call the function with self.assertRaises(JIRAError): - d.sync_with_jira( - issue=self.mock_issue, - config=self.mock_config - ) + d.sync_with_jira(issue=self.mock_issue, config=self.mock_config) # Assert all calls were made correctly mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config) @@ -615,21 +571,23 @@ def test_sync_with_jira_down(self, mock_create_jira_issue.assert_not_called() mock_existing_jira_issue_legacy.assert_not_called() - @mock.patch(PATH + 'get_jira_client') - @mock.patch(PATH + '_get_existing_jira_issue') - @mock.patch(PATH + '_update_jira_issue') - @mock.patch(PATH + '_create_jira_issue') - @mock.patch('jira.client.JIRA') - @mock.patch(PATH + '_get_existing_jira_issue_legacy') - @mock.patch(PATH + 'check_jira_status') - def test_sync_with_jira_no_matching(self, - mock_check_jira_status, - mock_existing_jira_issue_legacy, - mock_client, - mock_create_jira_issue, - mock_update_jira_issue, - mock_existing_jira_issue, - mock_get_jira_client): + @mock.patch(PATH + "get_jira_client") + @mock.patch(PATH + "_get_existing_jira_issue") + @mock.patch(PATH + "_update_jira_issue") + @mock.patch(PATH + "_create_jira_issue") + @mock.patch("jira.client.JIRA") + @mock.patch(PATH + "_get_existing_jira_issue_legacy") + @mock.patch(PATH + "check_jira_status") + def test_sync_with_jira_no_matching( + self, + mock_check_jira_status, + mock_existing_jira_issue_legacy, + mock_client, + mock_create_jira_issue, + mock_update_jira_issue, + mock_existing_jira_issue, + mock_get_jira_client, + ): """ Tests 'sync_with_jira' function where we do NOT find a matching issue This assumes we're not using the legacy matching anymore @@ -640,36 +598,37 @@ def test_sync_with_jira_no_matching(self, mock_check_jira_status.return_value = True # Call the function - d.sync_with_jira( - issue=self.mock_issue, - config=self.mock_config - ) + d.sync_with_jira(issue=self.mock_issue, config=self.mock_config) # Assert all calls were made correctly mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config) mock_update_jira_issue.assert_not_called() - mock_create_jira_issue.assert_called_with(mock_client, self.mock_issue, self.mock_config) + mock_create_jira_issue.assert_called_with( + mock_client, self.mock_issue, self.mock_config + ) mock_existing_jira_issue_legacy.assert_not_called() - @mock.patch(PATH + '_update_title') - @mock.patch(PATH + '_update_description') - @mock.patch(PATH + '_update_comments') - @mock.patch(PATH + '_update_tags') - @mock.patch(PATH + '_update_fixVersion') - @mock.patch(PATH + '_update_transition') - @mock.patch(PATH + '_update_assignee') - @mock.patch(PATH + '_update_on_close') - @mock.patch('jira.client.JIRA') - def test_update_jira_issue(self, - mock_client, - mock_update_on_close, - mock_update_assignee, - mock_update_transition, - mock_update_fixVersion, - mock_update_tags, - mock_update_comments, - mock_update_description, - mock_update_title): + @mock.patch(PATH + "_update_title") + @mock.patch(PATH + "_update_description") + @mock.patch(PATH + "_update_comments") + @mock.patch(PATH + "_update_tags") + @mock.patch(PATH + "_update_fixVersion") + @mock.patch(PATH + "_update_transition") + @mock.patch(PATH + "_update_assignee") + @mock.patch(PATH + "_update_on_close") + @mock.patch("jira.client.JIRA") + def test_update_jira_issue( + self, + mock_client, + mock_update_on_close, + mock_update_assignee, + mock_update_transition, + mock_update_fixVersion, + mock_update_tags, + mock_update_comments, + mock_update_description, + mock_update_title, + ): """ This tests '_update_jira_issue' function """ @@ -678,19 +637,15 @@ def test_update_jira_issue(self, existing=self.mock_downstream, issue=self.mock_issue, client=mock_client, - config=self.mock_config + config=self.mock_config, ) # Assert all calls were made correctly mock_update_comments.assert_called_with( - mock_client, - self.mock_downstream, - self.mock_issue + mock_client, self.mock_downstream, self.mock_issue ) mock_update_tags.assert_called_with( - self.mock_updates, - self.mock_downstream, - self.mock_issue + self.mock_updates, self.mock_downstream, self.mock_issue ) mock_update_fixVersion.assert_called_with( self.mock_updates, @@ -699,118 +654,109 @@ def test_update_jira_issue(self, mock_client, ) mock_update_description.assert_called_with( - self.mock_downstream, - self.mock_issue - ) - mock_update_title.assert_called_with( - self.mock_issue, - self.mock_downstream + self.mock_downstream, self.mock_issue ) + mock_update_title.assert_called_with(self.mock_issue, self.mock_downstream) mock_update_transition.assert_called_with( - mock_client, - self.mock_downstream, - self.mock_issue + mock_client, self.mock_downstream, self.mock_issue ) mock_update_on_close.assert_called_once() - @mock.patch('jira.client.JIRA') - def test_update_transition_JIRAError(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_update_transition_JIRAError(self, mock_client): """ This function tests the '_update_transition' function where Upstream issue status s not in existing.fields.description and transitioning the issue throws an error """ # Set up return values - self.mock_issue.status = 'Closed' - self.mock_downstream.fields.description = '' - mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}] + self.mock_issue.status = "Closed" + self.mock_downstream.fields.description = "" + mock_client.transitions.return_value = [ + {"name": "CUSTOM TRANSITION", "id": "1234"} + ] mock_client.transition_issue.side_effect = JIRAError # Call the function d._update_transition( - client=mock_client, - existing=self.mock_downstream, - issue=self.mock_issue + client=mock_client, existing=self.mock_downstream, issue=self.mock_issue ) # Assert all calls were made correctly mock_client.transitions.assert_called_with(self.mock_downstream) mock_client.transition_issue.assert_called_with(self.mock_downstream, 1234) - - @mock.patch('jira.client.JIRA') - def test_update_transition_not_found(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_update_transition_not_found(self, mock_client): """ This function tests the '_update_transition' function where Upstream issue status not in existing.fields.description and we can't find the appropriate closed status """ # Set up return values - self.mock_issue.status = 'Closed' - self.mock_issue.downstream['transition'] = 'bad_transition' - self.mock_downstream.fields.description = '' - mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}] + self.mock_issue.status = "Closed" + self.mock_issue.downstream["transition"] = "bad_transition" + self.mock_downstream.fields.description = "" + mock_client.transitions.return_value = [ + {"name": "CUSTOM TRANSITION", "id": "1234"} + ] # Call the function d._update_transition( - client=mock_client, - existing=self.mock_downstream, - issue=self.mock_issue + client=mock_client, existing=self.mock_downstream, issue=self.mock_issue ) # Assert all calls were made correctly mock_client.transitions.assert_called_with(self.mock_downstream) mock_client.transition_issue.assert_called_with(self.mock_downstream, 1234) - @mock.patch('jira.client.JIRA') - def test_update_transition_successful(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_update_transition_successful(self, mock_client): """ This function tests the '_update_transition' function where everything goes smoothly! """ # Set up return values - self.mock_issue.status = 'Closed' - self.mock_downstream.fields.description = '[test] Upstream issue status: Open' - mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}] + self.mock_issue.status = "Closed" + self.mock_downstream.fields.description = "[test] Upstream issue status: Open" + mock_client.transitions.return_value = [ + {"name": "CUSTOM TRANSITION", "id": "1234"} + ] # Call the function d._update_transition( - client=mock_client, - existing=self.mock_downstream, - issue=self.mock_issue + client=mock_client, existing=self.mock_downstream, issue=self.mock_issue ) # Assert all calls were made correctly mock_client.transitions.assert_called_with(self.mock_downstream) mock_client.transition_issue.assert_called_with(self.mock_downstream, 1234) - @mock.patch(PATH + '_comment_format') - @mock.patch(PATH + '_comment_matching') - @mock.patch('jira.client.JIRA') - def test_update_comments(self, - mock_client, - mock_comment_matching, - mock_comment_format): + @mock.patch(PATH + "_comment_format") + @mock.patch(PATH + "_comment_matching") + @mock.patch("jira.client.JIRA") + def test_update_comments( + self, mock_client, mock_comment_matching, mock_comment_format + ): """ This function tests the 'update_comments' function """ # Set up return values - mock_client.comments.return_value = 'mock_comments' - mock_comment_matching.return_value = ['mock_comments_d'] - mock_comment_format.return_value = 'mock_comment_body' + mock_client.comments.return_value = "mock_comments" + mock_comment_matching.return_value = ["mock_comments_d"] + mock_comment_format.return_value = "mock_comment_body" # Call the function d._update_comments( - client=mock_client, - existing=self.mock_downstream, - issue=self.mock_issue + client=mock_client, existing=self.mock_downstream, issue=self.mock_issue ) # Assert all calls were made correctly mock_client.comments.assert_called_with(self.mock_downstream) - mock_comment_matching.assert_called_with(self.mock_issue.comments, 'mock_comments') - mock_comment_format.assert_called_with('mock_comments_d') - mock_client.add_comment.assert_called_with(self.mock_downstream, 'mock_comment_body') + mock_comment_matching.assert_called_with( + self.mock_issue.comments, "mock_comments" + ) + mock_comment_format.assert_called_with("mock_comments_d") + mock_client.add_comment.assert_called_with( + self.mock_downstream, "mock_comment_body" + ) def test_update_fixVersion_JIRAError(self): """ @@ -831,9 +777,12 @@ def test_update_fixVersion_JIRAError(self): ) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'fixVersions': [{'name': 'fixVersion3'}, {'name': 'fixVersion4'}]}) - mock_client.add_comment(self.mock_downstream, f"Error updating fixVersion: {self.mock_issue.fixVersion}") - + {"fixVersions": [{"name": "fixVersion3"}, {"name": "fixVersion4"}]} + ) + mock_client.add_comment( + self.mock_downstream, + f"Error updating fixVersion: {self.mock_issue.fixVersion}", + ) def test_update_fixVersion_no_api_call(self): """ @@ -872,14 +821,13 @@ def test_update_fixVersion_successful(self): ) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'fixVersions': [{'name': 'fixVersion3'}, {'name': 'fixVersion4'}]}) + {"fixVersions": [{"name": "fixVersion3"}, {"name": "fixVersion4"}]} + ) mock_client.add_comment.assert_not_called() - @mock.patch(PATH + 'assign_user') - @mock.patch('jira.client.JIRA') - def test_update_assignee_assignee(self, - mock_client, - mock_assign_user): + @mock.patch(PATH + "assign_user") + @mock.patch("jira.client.JIRA") + def test_update_assignee_assignee(self, mock_client, mock_assign_user): """ This function tests the 'update_assignee' function where issue.assignee exists """ @@ -888,21 +836,17 @@ def test_update_assignee_assignee(self, client=mock_client, existing=self.mock_downstream, issue=self.mock_issue, - updates=[{'assignee': {'overwrite': True}}] + updates=[{"assignee": {"overwrite": True}}], ) # Assert all calls were made correctly mock_assign_user.assert_called_with( - mock_client, - self.mock_issue, - self.mock_downstream + mock_client, self.mock_issue, self.mock_downstream ) - @mock.patch(PATH + 'assign_user') - @mock.patch('jira.client.JIRA') - def test_update_assignee_no_assignee(self, - mock_client, - mock_assign_user): + @mock.patch(PATH + "assign_user") + @mock.patch("jira.client.JIRA") + def test_update_assignee_no_assignee(self, mock_client, mock_assign_user): """ This function tests the '_update_assignee' function where issue.assignee does not exist """ @@ -914,22 +858,17 @@ def test_update_assignee_no_assignee(self, client=mock_client, existing=self.mock_downstream, issue=self.mock_issue, - updates=[{'assignee': {'overwrite': True}}] + updates=[{"assignee": {"overwrite": True}}], ) # Assert all calls were made correctly mock_assign_user.assert_called_with( - mock_client, - self.mock_issue, - self.mock_downstream, - remove_all=True + mock_client, self.mock_issue, self.mock_downstream, remove_all=True ) - @mock.patch(PATH + 'assign_user') - @mock.patch('jira.client.JIRA') - def test_update_assignee_no_overwrite(self, - mock_client, - mock_assign_user): + @mock.patch(PATH + "assign_user") + @mock.patch("jira.client.JIRA") + def test_update_assignee_no_overwrite(self, mock_client, mock_assign_user): """ This function tests the '_update_assignee' function where overwrite is false """ @@ -941,70 +880,63 @@ def test_update_assignee_no_overwrite(self, client=mock_client, existing=self.mock_downstream, issue=self.mock_issue, - updates=[{'assignee': {'overwrite': False}}] + updates=[{"assignee": {"overwrite": False}}], ) # Assert all calls were made correctly mock_assign_user.assert_called_with( - mock_client, - self.mock_issue, - self.mock_downstream + mock_client, self.mock_issue, self.mock_downstream ) - - @mock.patch(PATH + 'verify_tags') - @mock.patch(PATH + '_label_matching') - def test_update_tags(self, - mock_label_matching, - mock_verify_tags): + @mock.patch(PATH + "verify_tags") + @mock.patch(PATH + "_label_matching") + def test_update_tags(self, mock_label_matching, mock_verify_tags): """ This function tests the '_update_tags' function """ # Set up return values - mock_label_matching.return_value = 'mock_updated_labels' - mock_verify_tags.return_value = ['mock_verified_tags'] + mock_label_matching.return_value = "mock_updated_labels" + mock_verify_tags.return_value = ["mock_verified_tags"] # Call the function d._update_tags( updates=self.mock_updates, existing=self.mock_downstream, - issue=self.mock_issue + issue=self.mock_issue, ) # Assert all calls were made correctly mock_label_matching.assert_called_with( - self.mock_issue.tags, - self.mock_downstream.fields.labels + self.mock_issue.tags, self.mock_downstream.fields.labels + ) + mock_verify_tags.assert_called_with("mock_updated_labels") + self.mock_downstream.update.assert_called_with( + {"labels": ["mock_verified_tags"]} ) - mock_verify_tags.assert_called_with('mock_updated_labels') - self.mock_downstream.update.assert_called_with({'labels': ['mock_verified_tags']}) - @mock.patch(PATH + 'verify_tags') - @mock.patch(PATH + '_label_matching') - def test_update_tags_no_api_call(self, - mock_label_matching, - mock_verify_tags): + @mock.patch(PATH + "verify_tags") + @mock.patch(PATH + "_label_matching") + def test_update_tags_no_api_call(self, mock_label_matching, mock_verify_tags): """ This function tests the '_update_tags' function where the existing tags are the same as the new ones """ # Set up return values - mock_label_matching.return_value = 'mock_updated_labels' - mock_verify_tags.return_value = ['tag3', 'tag4'] + mock_label_matching.return_value = "mock_updated_labels" + mock_verify_tags.return_value = ["tag3", "tag4"] # Call the function d._update_tags( updates=self.mock_updates, existing=self.mock_downstream, - issue=self.mock_issue + issue=self.mock_issue, ) # Assert all calls were made correctly mock_label_matching.assert_called_with( - self.mock_issue.tags, - self.mock_downstream.fields.labels + self.mock_issue.tags, self.mock_downstream.fields.labels ) - mock_verify_tags.assert_called_with('mock_updated_labels') + mock_verify_tags.assert_called_with("mock_updated_labels") self.mock_downstream.update.assert_not_called() def test_update_description_update(self): @@ -1012,164 +944,165 @@ def test_update_description_update(self): This function tests '_update_description' where we just have to update the contents of the description """ # Set up return values - self.mock_downstream.fields.description = '[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote} test {quote}' + self.mock_downstream.fields.description = "[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote} test {quote}" # Call the function - d._update_description( - existing=self.mock_downstream, - issue=self.mock_issue - ) + d._update_description(existing=self.mock_downstream, issue=self.mock_issue) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'description': '[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}'}) + { + "description": "[1234] Upstream Reporter: mock_user\nUpstream issue status: Open\nUpstream description: {quote}mock_content{quote}" + } + ) def test_update_description_add_field(self): """ This function tests '_update_description' where we just have to add a description field """ # Set up return values - self.mock_downstream.fields.description = '[123] Upstream Reporter: mock_user\n' \ - 'Upstream description: {quote} test {quote}' + self.mock_downstream.fields.description = ( + "[123] Upstream Reporter: mock_user\n" + "Upstream description: {quote} test {quote}" + ) # Call the function - d._update_description( - existing=self.mock_downstream, - issue=self.mock_issue - ) + d._update_description(existing=self.mock_downstream, issue=self.mock_issue) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'description': '[1234] Upstream Reporter: mock_user\n' - 'Upstream issue status: Open\n' - 'Upstream description: {quote}mock_content{quote}'}) + { + "description": "[1234] Upstream Reporter: mock_user\n" + "Upstream issue status: Open\n" + "Upstream description: {quote}mock_content{quote}" + } + ) def test_update_description_add_reporter(self): """ This function tests '_update_description' where we have to add a description and upstream reporter field """ # Set up return values - self.mock_downstream.fields.description = '[123] Upstream issue status: Open\n' - self.mock_issue.status = 'Open' - self.mock_issue.id = '123' - self.mock_issue.reporter = {'fullname': 'mock_user'} + self.mock_downstream.fields.description = "[123] Upstream issue status: Open\n" + self.mock_issue.status = "Open" + self.mock_issue.id = "123" + self.mock_issue.reporter = {"fullname": "mock_user"} # Call the function - d._update_description( - existing=self.mock_downstream, - issue=self.mock_issue - ) + d._update_description(existing=self.mock_downstream, issue=self.mock_issue) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'description': '[123] Upstream Reporter: mock_user\n' - 'Upstream issue status: Open\n' - 'Upstream description: {quote}mock_content{quote}'}) + { + "description": "[123] Upstream Reporter: mock_user\n" + "Upstream issue status: Open\n" + "Upstream description: {quote}mock_content{quote}" + } + ) def test_update_description_add_reporter_no_status(self): """ This function tests '_update_description' where we have to add reporter and description without status """ # Set up return values - self.mock_downstream.fields.description = '' - self.mock_issue.downstream['issue_updates'] = [ - u for u in self.mock_issue.downstream['issue_updates'] if 'transition' not in u] + self.mock_downstream.fields.description = "" + self.mock_issue.downstream["issue_updates"] = [ + u + for u in self.mock_issue.downstream["issue_updates"] + if "transition" not in u + ] # Call the function - d._update_description( - existing=self.mock_downstream, - issue=self.mock_issue - ) + d._update_description(existing=self.mock_downstream, issue=self.mock_issue) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'description': '[1234] Upstream Reporter: mock_user\n' - 'Upstream description: {quote}mock_content{quote}'}) + { + "description": "[1234] Upstream Reporter: mock_user\n" + "Upstream description: {quote}mock_content{quote}" + } + ) - @mock.patch(PATH + 'datetime') - def test_update_description_add_description(self, - mock_datetime): + @mock.patch(PATH + "datetime") + def test_update_description_add_description(self, mock_datetime): """ This function tests '_update_description' where we have a reporter and status already """ # Set up return values - self.mock_downstream.fields.description = '[123] Upstream issue status: Open\n' \ - '[123] Upstream Reporter: mock_user\n' - self.mock_issue.status = 'Open' - self.mock_issue.id = '123' - self.mock_issue.reporter = {'fullname': 'mock_user'} + self.mock_downstream.fields.description = ( + "[123] Upstream issue status: Open\n" "[123] Upstream Reporter: mock_user\n" + ) + self.mock_issue.status = "Open" + self.mock_issue.id = "123" + self.mock_issue.reporter = {"fullname": "mock_user"} mock_datetime.today.return_value = self.mock_today # Call the function - d._update_description( - existing=self.mock_downstream, - issue=self.mock_issue - ) + d._update_description(existing=self.mock_downstream, issue=self.mock_issue) # Assert all calls were made correctly self.mock_downstream.update.assert_called_with( - {'description': '[123] Upstream Reporter: mock_user\n' - 'Upstream issue status: Open\n' - 'Upstream description: {quote}mock_content{quote}'}) + { + "description": "[123] Upstream Reporter: mock_user\n" + "Upstream issue status: Open\n" + "Upstream description: {quote}mock_content{quote}" + } + ) def test_verify_tags(self): """ This function tests 'verify_tags' function """ # Call the function - response = d.verify_tags( - tags=['this is a tag'] - ) + response = d.verify_tags(tags=["this is a tag"]) # Assert everything was called correctly - self.assertEqual(response, ['this_is_a_tag']) - - @mock.patch(PATH + 'get_jira_client') - @mock.patch(PATH + '_matching_jira_issue_query') - @mock.patch(PATH + '_close_as_duplicate') - @mock.patch('jira.client.JIRA') - @mock.patch(PATH + 'check_jira_status') - def test_close_duplicates_no_matching(self, - mock_check_jira_status, - mock_client, - mock_close_as_duplicate, - mock_matching_jira_issue_query, - mock_get_jira_client): + self.assertEqual(response, ["this_is_a_tag"]) + + @mock.patch(PATH + "get_jira_client") + @mock.patch(PATH + "_matching_jira_issue_query") + @mock.patch(PATH + "_close_as_duplicate") + @mock.patch("jira.client.JIRA") + @mock.patch(PATH + "check_jira_status") + def test_close_duplicates_no_matching( + self, + mock_check_jira_status, + mock_client, + mock_close_as_duplicate, + mock_matching_jira_issue_query, + mock_get_jira_client, + ): """ This tests 'close_duplicates' function where len(results) <= 1 """ # Set up return values mock_get_jira_client.return_value = mock_client - mock_matching_jira_issue_query.return_value = ['only_one_response'] + mock_matching_jira_issue_query.return_value = ["only_one_response"] mock_check_jira_status.return_value = True # Call the function - response = d.close_duplicates( - issue=self.mock_issue, - config=self.mock_config - ) + response = d.close_duplicates(issue=self.mock_issue, config=self.mock_config) # Assert everything was called correctly mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config) mock_matching_jira_issue_query.assert_called_with( - mock_client, - self.mock_issue, - self.mock_config, - free=True + mock_client, self.mock_issue, self.mock_config, free=True ) mock_close_as_duplicate.assert_not_called() self.assertEqual(None, response) - @mock.patch(PATH + 'get_jira_client') - @mock.patch(PATH + '_matching_jira_issue_query') - @mock.patch(PATH + '_close_as_duplicate') - @mock.patch('jira.client.JIRA') - @mock.patch(PATH + 'check_jira_status') - def test_close_duplicates(self, - mock_check_jira_status, - mock_client, - mock_close_as_duplicate, - mock_matching_jira_issue_query, - mock_get_jira_client): + @mock.patch(PATH + "get_jira_client") + @mock.patch(PATH + "_matching_jira_issue_query") + @mock.patch(PATH + "_close_as_duplicate") + @mock.patch("jira.client.JIRA") + @mock.patch(PATH + "check_jira_status") + def test_close_duplicates( + self, + mock_check_jira_status, + mock_client, + mock_close_as_duplicate, + mock_matching_jira_issue_query, + mock_get_jira_client, + ): """ This tests 'close_duplicates' function where len(results) > 1 """ @@ -1181,47 +1114,38 @@ def test_close_duplicates(self, mock_check_jira_status.return_value = True # Call the function - response = d.close_duplicates( - issue=self.mock_issue, - config=self.mock_config - ) + response = d.close_duplicates(issue=self.mock_issue, config=self.mock_config) # Assert everything was called correctly mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config) mock_matching_jira_issue_query.assert_called_with( - mock_client, - self.mock_issue, - self.mock_config, - free=True + mock_client, self.mock_issue, self.mock_config, free=True ) mock_close_as_duplicate.assert_called_with( - mock_client, - mock_item, - mock_item, - self.mock_config + mock_client, mock_item, mock_item, self.mock_config ) self.assertEqual(None, response) - @mock.patch('jira.client.JIRA') - def test_close_as_duplicate_errors(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_close_as_duplicate_errors(self, mock_client): """ This tests '_close_as_duplicate' function where client.transition_issue throws an exception """ + # Set up return values - class HTTPExceptionHelper(): + class HTTPExceptionHelper: text = "Field 'resolution' cannot be set" class HTTPException(Exception): response = HTTPExceptionHelper mock_duplicate = MagicMock() - mock_duplicate.permalink.return_value = 'mock_url' - mock_duplicate.key = 'mock_key' + mock_duplicate.permalink.return_value = "mock_url" + mock_duplicate.key = "mock_key" mock_keeper = MagicMock() - mock_keeper.key = 'mock_key' - mock_keeper.permalink.return_value = 'mock_url' - mock_client.transitions.return_value = [{'name': 'Dropped', 'id': '1234'}] + mock_keeper.key = "mock_key" + mock_keeper.permalink.return_value = "mock_url" + mock_client.transitions.return_value = [{"name": "Dropped", "id": "1234"}] mock_client.comments.return_value = [] mock_client.transition_issue.side_effect = HTTPException @@ -1230,39 +1154,35 @@ class HTTPException(Exception): client=mock_client, duplicate=mock_duplicate, keeper=mock_keeper, - config=self.mock_config + config=self.mock_config, ) # Assert everything was called correctly mock_client.comments.assert_any_call(mock_keeper) mock_client.comments.assert_any_call(mock_duplicate) mock_client.transitions.assert_called_with(mock_duplicate) - mock_client.add_comment.assert_any_call(mock_duplicate, 'Marking as duplicate of mock_key') - mock_client.add_comment.assert_any_call(mock_keeper, 'mock_key is a duplicate.') - mock_client.transition_issue.assert_any_call( - mock_duplicate, - '1234', - resolution={'name': 'Duplicate'} + mock_client.add_comment.assert_any_call( + mock_duplicate, "Marking as duplicate of mock_key" ) + mock_client.add_comment.assert_any_call(mock_keeper, "mock_key is a duplicate.") mock_client.transition_issue.assert_any_call( - mock_duplicate, - '1234' + mock_duplicate, "1234", resolution={"name": "Duplicate"} ) + mock_client.transition_issue.assert_any_call(mock_duplicate, "1234") - @mock.patch('jira.client.JIRA') - def test_close_as_duplicate(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_close_as_duplicate(self, mock_client): """ This tests '_close_as_duplicate' function where everything goes smoothly """ # Set up return values mock_duplicate = MagicMock() - mock_duplicate.permalink.return_value = 'mock_url' - mock_duplicate.key = 'mock_key' + mock_duplicate.permalink.return_value = "mock_url" + mock_duplicate.key = "mock_key" mock_keeper = MagicMock() - mock_keeper.key = 'mock_key' - mock_keeper.permalink.return_value = 'mock_url' - mock_client.transitions.return_value = [{'name': 'Dropped', 'id': '1234'}] + mock_keeper.key = "mock_key" + mock_keeper.permalink.return_value = "mock_url" + mock_client.transitions.return_value = [{"name": "Dropped", "id": "1234"}] mock_client.comments.return_value = [] # Call the function @@ -1270,50 +1190,53 @@ def test_close_as_duplicate(self, client=mock_client, duplicate=mock_duplicate, keeper=mock_keeper, - config=self.mock_config + config=self.mock_config, ) # Assert everything was called correctly mock_client.comments.assert_any_call(mock_keeper) mock_client.comments.assert_any_call(mock_duplicate) mock_client.transitions.assert_called_with(mock_duplicate) - mock_client.add_comment.assert_any_call(mock_duplicate, 'Marking as duplicate of mock_key') - mock_client.add_comment.assert_any_call(mock_keeper, 'mock_key is a duplicate.') + mock_client.add_comment.assert_any_call( + mock_duplicate, "Marking as duplicate of mock_key" + ) + mock_client.add_comment.assert_any_call(mock_keeper, "mock_key is a duplicate.") mock_client.transition_issue.assert_called_with( - mock_duplicate, - '1234', - resolution={'name': 'Duplicate'} + mock_duplicate, "1234", resolution={"name": "Duplicate"} ) - @mock.patch(PATH + 'alert_user_of_duplicate_issues') - @mock.patch(PATH + 'find_username') - @mock.patch(PATH + 'check_comments_for_duplicate') - @mock.patch('jira.client.JIRA') - def test_matching_jira_issue_query(self, - mock_client, - mock_check_comments_for_duplicates, - mock_find_username, - mock_alert_user_of_duplicate_issues): + @mock.patch(PATH + "alert_user_of_duplicate_issues") + @mock.patch(PATH + "find_username") + @mock.patch(PATH + "check_comments_for_duplicate") + @mock.patch("jira.client.JIRA") + def test_matching_jira_issue_query( + self, + mock_client, + mock_check_comments_for_duplicates, + mock_find_username, + mock_alert_user_of_duplicate_issues, + ): """ This tests '_matching_jira_query' function """ # Set up return values mock_downstream_issue = MagicMock() - self.mock_issue.upstream_title = 'mock_upstream_title' + self.mock_issue.upstream_title = "mock_upstream_title" mock_downstream_issue.fields.description = self.mock_issue.id bad_downstream_issue = MagicMock() - bad_downstream_issue.fields.description = 'bad' - bad_downstream_issue.fields.summary = 'bad' - mock_client.search_issues.return_value = [mock_downstream_issue, bad_downstream_issue] + bad_downstream_issue.fields.description = "bad" + bad_downstream_issue.fields.summary = "bad" + mock_client.search_issues.return_value = [ + mock_downstream_issue, + bad_downstream_issue, + ] mock_check_comments_for_duplicates.return_value = True - mock_find_username.return_value = 'mock_username' + mock_find_username.return_value = "mock_username" mock_alert_user_of_duplicate_issues.return_value = True # Call the function response = d._matching_jira_issue_query( - client=mock_client, - issue=self.mock_issue, - config=self.mock_config + client=mock_client, issue=self.mock_issue, config=self.mock_config ) # Assert everything was called correctly @@ -1323,45 +1246,43 @@ def test_matching_jira_issue_query(self, [mock_downstream_issue], mock_client.search_issues.return_value, self.mock_config, - mock_client + mock_client, ) mock_client.search_issues.assert_called_with( 'issueFunction in linkedIssuesOfRemote("Upstream issue")' - ' and issueFunction in linkedIssuesOfRemote("mock_url")') - mock_check_comments_for_duplicates.assert_called_with( - mock_client, - mock_downstream_issue, - 'mock_username' + ' and issueFunction in linkedIssuesOfRemote("mock_url")' ) - mock_find_username.assert_called_with( - self.mock_issue, - self.mock_config + mock_check_comments_for_duplicates.assert_called_with( + mock_client, mock_downstream_issue, "mock_username" ) + mock_find_username.assert_called_with(self.mock_issue, self.mock_config) - @mock.patch(PATH + 'jinja2') - @mock.patch(PATH + 'send_mail') - @mock.patch('jira.client.JIRA') - def test_alert_user(self, - mock_client, - mock_mailer, - mock_jinja,): + @mock.patch(PATH + "jinja2") + @mock.patch(PATH + "send_mail") + @mock.patch("jira.client.JIRA") + def test_alert_user( + self, + mock_client, + mock_mailer, + mock_jinja, + ): """ This tests 'alert_user_of_duplicate_issues' function """ # Set up return values mock_downstream_issue = MagicMock() - mock_downstream_issue.key = 'mock_key' + mock_downstream_issue.key = "mock_key" bad_downstream_issue = MagicMock() - bad_downstream_issue.key = 'mock_key' - bad_downstream_issue.fields.status.name = 'To Do' + bad_downstream_issue.key = "mock_key" + bad_downstream_issue.fields.status.name = "To Do" mock_results_of_query = [mock_downstream_issue, bad_downstream_issue] mock_search_user_result = MagicMock() - mock_search_user_result.displayName = 'mock_name' - mock_search_user_result.emailAddress = 'mock_email' + mock_search_user_result.displayName = "mock_name" + mock_search_user_result.emailAddress = "mock_email" mock_client.search_users.return_value = [mock_search_user_result] - mock_template = MagicMock(name='template') - mock_template.render.return_value = 'mock_html_text' - mock_template_env = MagicMock(name='templateEnv') + mock_template = MagicMock(name="template") + mock_template.render.return_value = "mock_html_text" + mock_template_env = MagicMock(name="templateEnv") mock_template_env.get_template.return_value = mock_template mock_jinja.Environment.return_value = mock_template_env @@ -1371,50 +1292,58 @@ def test_alert_user(self, final_result=[mock_downstream_issue], results_of_query=mock_results_of_query, config=self.mock_config, - client=mock_client + client=mock_client, ) # Assert everything was called correctly - mock_client.search_users.assert_any_call('mock_owner') - mock_client.search_users.assert_any_call('mock_admin') + mock_client.search_users.assert_any_call("mock_owner") + mock_client.search_users.assert_any_call("mock_admin") mock_template.render.assert_called_with( - admins=[{'name': 'mock_name', 'email': 'mock_email'}], - duplicate_issues=[{'url': 'mock_server/browse/mock_key', 'title': 'mock_key'}], + admins=[{"name": "mock_name", "email": "mock_email"}], + duplicate_issues=[ + {"url": "mock_server/browse/mock_key", "title": "mock_key"} + ], issue=self.mock_issue, - selected_issue={'url': 'mock_server/browse/mock_key', 'title': 'mock_key'}, - user={'name': 'mock_name', 'email': 'mock_email'}) - mock_mailer().send.asset_called_with('test') - - @mock.patch(PATH + 'jinja2') - @mock.patch(PATH + 'send_mail') - @mock.patch('jira.client.JIRA') - def test_alert_user_multiple_users(self, - mock_client, - mock_mailer, - mock_jinja, ): + selected_issue={"url": "mock_server/browse/mock_key", "title": "mock_key"}, + user={"name": "mock_name", "email": "mock_email"}, + ) + mock_mailer().send.asset_called_with("test") + + @mock.patch(PATH + "jinja2") + @mock.patch(PATH + "send_mail") + @mock.patch("jira.client.JIRA") + def test_alert_user_multiple_users( + self, + mock_client, + mock_mailer, + mock_jinja, + ): """ This tests 'alert_user_of_duplicate_issues' function where searching returns multiple users """ # Set up return values mock_downstream_issue = MagicMock() - mock_downstream_issue.key = 'mock_key' + mock_downstream_issue.key = "mock_key" bad_downstream_issue = MagicMock() - bad_downstream_issue.key = 'mock_key' - bad_downstream_issue.fields.status.name = 'To Do' + bad_downstream_issue.key = "mock_key" + bad_downstream_issue.fields.status.name = "To Do" mock_results_of_query = [mock_downstream_issue, bad_downstream_issue] mock_search_user_result1 = MagicMock() - mock_search_user_result1.displayName = 'bad_name' - mock_search_user_result1.emailAddress = 'bad_email' - mock_search_user_result1.key = 'bad_owner' + mock_search_user_result1.displayName = "bad_name" + mock_search_user_result1.emailAddress = "bad_email" + mock_search_user_result1.key = "bad_owner" mock_search_user_result2 = MagicMock() - mock_search_user_result2.displayName = 'mock_name' - mock_search_user_result2.emailAddress = 'mock_email' - mock_search_user_result2.key = 'mock_owner' - mock_client.search_users.return_value = [mock_search_user_result1, mock_search_user_result2] - mock_template = MagicMock(name='template') - mock_template.render.return_value = 'mock_html_text' - mock_template_env = MagicMock(name='templateEnv') + mock_search_user_result2.displayName = "mock_name" + mock_search_user_result2.emailAddress = "mock_email" + mock_search_user_result2.key = "mock_owner" + mock_client.search_users.return_value = [ + mock_search_user_result1, + mock_search_user_result2, + ] + mock_template = MagicMock(name="template") + mock_template.render.return_value = "mock_html_text" + mock_template_env = MagicMock(name="templateEnv") mock_template_env.get_template.return_value = mock_template mock_jinja.Environment.return_value = mock_template_env @@ -1424,74 +1353,71 @@ def test_alert_user_multiple_users(self, final_result=[mock_downstream_issue], results_of_query=mock_results_of_query, config=self.mock_config, - client=mock_client + client=mock_client, ) # Assert everything was called correctly - mock_client.search_users.assert_any_call('mock_owner') - mock_client.search_users.assert_any_call('mock_admin') + mock_client.search_users.assert_any_call("mock_owner") + mock_client.search_users.assert_any_call("mock_admin") mock_template.render.assert_called_with( - admins=[{'name': 'mock_name', 'email': 'mock_email'}], - duplicate_issues=[{'url': 'mock_server/browse/mock_key', 'title': 'mock_key'}], + admins=[{"name": "mock_name", "email": "mock_email"}], + duplicate_issues=[ + {"url": "mock_server/browse/mock_key", "title": "mock_key"} + ], issue=self.mock_issue, - selected_issue={'url': 'mock_server/browse/mock_key', 'title': 'mock_key'}, - user={'name': 'mock_name', 'email': 'mock_email'}) - mock_mailer().send.asset_called_with('test') + selected_issue={"url": "mock_server/browse/mock_key", "title": "mock_key"}, + user={"name": "mock_name", "email": "mock_email"}, + ) + mock_mailer().send.asset_called_with("test") def test_find_username(self): """ Tests 'find_username' function """ # Call the function - response = d.find_username( - self.mock_issue, - self.mock_config - ) + response = d.find_username(self.mock_issue, self.mock_config) # Assert everything was called correctly - self.assertEqual(response, 'mock_user') + self.assertEqual(response, "mock_user") - @mock.patch('jira.client.JIRA') - def test_check_comments_for_duplicates(self, - mock_client): + @mock.patch("jira.client.JIRA") + def test_check_comments_for_duplicates(self, mock_client): """ Tests 'check_comments_for_duplicates' function """ # Set up return values mock_comment = MagicMock() - mock_comment.body = 'Marking as duplicate of TEST-1234' - mock_comment.author.name = 'mock_user' + mock_comment.body = "Marking as duplicate of TEST-1234" + mock_comment.author.name = "mock_user" mock_client.comments.return_value = [mock_comment] - mock_client.issue.return_value = 'Successful Call!' + mock_client.issue.return_value = "Successful Call!" # Call the function response = d.check_comments_for_duplicate( - client=mock_client, - result=self.mock_downstream, - username='mock_user' + client=mock_client, result=self.mock_downstream, username="mock_user" ) # Assert everything was called correctly - self.assertEqual(response, 'Successful Call!') + self.assertEqual(response, "Successful Call!") mock_client.comments.assert_called_with(self.mock_downstream) - mock_client.issue.assert_called_with('TEST-1234') + mock_client.issue.assert_called_with("TEST-1234") - @mock.patch(PATH + '_comment_format') - @mock.patch(PATH + '_comment_format_legacy') - def test_find_comment_in_jira_legacy(self, - mock_comment_format_legacy, - mock_comment_format): + @mock.patch(PATH + "_comment_format") + @mock.patch(PATH + "_comment_format_legacy") + def test_find_comment_in_jira_legacy( + self, mock_comment_format_legacy, mock_comment_format + ): """ This function tests '_find_comment_in_jira' where we find a legacy comment """ # Set up return values - mock_comment_format.return_value = 'mock_comment_body' - mock_comment_format_legacy.return_value = 'mock_legacy_comment_body' + mock_comment_format.return_value = "mock_comment_body" + mock_comment_format_legacy.return_value = "mock_legacy_comment_body" mock_jira_comment = MagicMock() - mock_jira_comment.raw = {'body': 'mock_legacy_comment_body'} + mock_jira_comment.raw = {"body": "mock_legacy_comment_body"} mock_comment = { - 'id': '12345', - 'date_created': datetime(2019, 8, 8, tzinfo=timezone.utc) + "id": "12345", + "date_created": datetime(2019, 8, 8, tzinfo=timezone.utc), } # Call the function @@ -1502,22 +1428,22 @@ def test_find_comment_in_jira_legacy(self, mock_comment_format.assert_called_with(mock_comment) self.assertEqual(response, mock_jira_comment) - @mock.patch(PATH + '_comment_format') - @mock.patch(PATH + '_comment_format_legacy') - def test_find_comment_in_jira_id(self, - mock_comment_format_legacy, - mock_comment_format): + @mock.patch(PATH + "_comment_format") + @mock.patch(PATH + "_comment_format_legacy") + def test_find_comment_in_jira_id( + self, mock_comment_format_legacy, mock_comment_format + ): """ This function tests '_find_comment_in_jira' where we match an ID """ # Set up return values - mock_comment_format.return_value = 'mock_comment_body' - mock_comment_format_legacy.return_value = 'mock_legacy_comment_body' + mock_comment_format.return_value = "mock_comment_body" + mock_comment_format_legacy.return_value = "mock_legacy_comment_body" mock_jira_comment = MagicMock() - mock_jira_comment.raw = {'body': '12345'} + mock_jira_comment.raw = {"body": "12345"} mock_comment = { - 'id': '12345', - 'date_created': datetime(2019, 8, 8, tzinfo=timezone.utc) + "id": "12345", + "date_created": datetime(2019, 8, 8, tzinfo=timezone.utc), } # Call the function @@ -1528,22 +1454,22 @@ def test_find_comment_in_jira_id(self, mock_comment_format.assert_called_with(mock_comment) self.assertEqual(response, mock_jira_comment) - @mock.patch(PATH + '_comment_format') - @mock.patch(PATH + '_comment_format_legacy') - def test_find_comment_in_jira_old_comment(self, - mock_comment_format_legacy, - mock_comment_format): + @mock.patch(PATH + "_comment_format") + @mock.patch(PATH + "_comment_format_legacy") + def test_find_comment_in_jira_old_comment( + self, mock_comment_format_legacy, mock_comment_format + ): """ This function tests '_find_comment_in_jira' where we find a old comment """ # Set up return values - mock_comment_format.return_value = 'mock_comment_body' - mock_comment_format_legacy.return_value = 'mock_legacy_comment_body' + mock_comment_format.return_value = "mock_comment_body" + mock_comment_format_legacy.return_value = "mock_legacy_comment_body" mock_jira_comment = MagicMock() - mock_jira_comment.raw = {'body': 'old_comment'} + mock_jira_comment.raw = {"body": "old_comment"} mock_comment = { - 'id': '12345', - 'date_created': datetime(2019, 1, 1, tzinfo=timezone.utc) + "id": "12345", + "date_created": datetime(2019, 1, 1, tzinfo=timezone.utc), } # Call the function @@ -1554,20 +1480,20 @@ def test_find_comment_in_jira_old_comment(self, mock_comment_format.assert_called_with(mock_comment) self.assertEqual(response, mock_jira_comment) - @mock.patch(PATH + '_comment_format') - @mock.patch(PATH + '_comment_format_legacy') - def test_find_comment_in_jira_none(self, - mock_comment_format_legacy, - mock_comment_format): + @mock.patch(PATH + "_comment_format") + @mock.patch(PATH + "_comment_format_legacy") + def test_find_comment_in_jira_none( + self, mock_comment_format_legacy, mock_comment_format + ): """ This function tests '_find_comment_in_jira' where we return None """ # Set up return values - mock_comment_format.return_value = 'mock_comment_body' - mock_comment_format_legacy.return_value = 'mock_legacy_comment_body' + mock_comment_format.return_value = "mock_comment_body" + mock_comment_format_legacy.return_value = "mock_legacy_comment_body" mock_comment = { - 'id': '12345', - 'date_created': datetime(2019, 1, 1, tzinfo=timezone.utc) + "id": "12345", + "date_created": datetime(2019, 1, 1, tzinfo=timezone.utc), } # Call the function @@ -1591,7 +1517,9 @@ def test_check_jira_status_false(self): # Assert everything was called correctly self.assertEqual(response, False) - mock_jira_client.search_issues.assert_called_with("issueFunction in linkedIssuesOfRemote('*')") + mock_jira_client.search_issues.assert_called_with( + "issueFunction in linkedIssuesOfRemote('*')" + ) def test_check_jira_status_true(self): """ @@ -1599,14 +1527,16 @@ def test_check_jira_status_true(self): """ # Set up return values mock_jira_client = MagicMock() - mock_jira_client.search_issues.return_value = ['some', 'values'] + mock_jira_client.search_issues.return_value = ["some", "values"] # Call the function response = d.check_jira_status(mock_jira_client) # Assert everything was called correctly self.assertEqual(response, True) - mock_jira_client.search_issues.assert_called_with("issueFunction in linkedIssuesOfRemote('*')") + mock_jira_client.search_issues.assert_called_with( + "issueFunction in linkedIssuesOfRemote('*')" + ) def test_update_on_close_update(self): """ @@ -1615,7 +1545,7 @@ def test_update_on_close_update(self): """ # Set up return values self.mock_downstream.fields.description = "" - self.mock_issue.status = 'Closed' + self.mock_issue.status = "Closed" updates = [{"on_close": {"apply_labels": ["closed-upstream"]}}] # Call the function @@ -1623,8 +1553,8 @@ def test_update_on_close_update(self): # Assert everything was called correctly self.mock_downstream.update.assert_called_with( - {'labels': - ["closed-upstream", "tag3", "tag4"]}) + {"labels": ["closed-upstream", "tag3", "tag4"]} + ) def test_update_on_close_no_change(self): """ @@ -1632,7 +1562,7 @@ def test_update_on_close_no_change(self): "apply_labels" configuration but there is no update required. """ # Set up return values - self.mock_issue.status = 'Closed' + self.mock_issue.status = "Closed" updates = [{"on_close": {"apply_labels": ["tag4"]}}] # Call the function @@ -1647,7 +1577,7 @@ def test_update_on_close_no_action(self): "apply_labels" configuration. """ # Set up return values - self.mock_issue.status = 'Closed' + self.mock_issue.status = "Closed" updates = [{"on_close": {"some_other_action": None}}] # Call the function @@ -1662,7 +1592,7 @@ def test_update_on_close_no_config(self): configuration for close events. """ # Set up return values - self.mock_issue.status = 'Closed' + self.mock_issue.status = "Closed" updates = ["description"] # Call the function @@ -1671,56 +1601,71 @@ def test_update_on_close_no_config(self): # Assert everything was called correctly self.mock_downstream.update.assert_not_called() - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_update_github_project_fields_storypoints(self, mock_client): """ This function tests `_update_github_project_fields` with story points value. """ - github_project_fields = { - "storypoints": { - "gh_field": "Estimate" - }} - d._update_github_project_fields(mock_client, self.mock_downstream, self.mock_issue, - github_project_fields, self.mock_config) - self.mock_downstream.update.assert_called_with({'customfield_12310243': 2}) - - @mock.patch('jira.client.JIRA') + github_project_fields = {"storypoints": {"gh_field": "Estimate"}} + d._update_github_project_fields( + mock_client, + self.mock_downstream, + self.mock_issue, + github_project_fields, + self.mock_config, + ) + self.mock_downstream.update.assert_called_with({"customfield_12310243": 2}) + + @mock.patch("jira.client.JIRA") def test_update_github_project_fields_storypoints_bad(self, mock_client): """This function tests `_update_github_project_fields` with a bad (non-numeric) story points value. """ github_project_fields = {"storypoints": {"gh_field": "Estimate"}} - for bad_sp in [None, '', 'bad_value']: + for bad_sp in [None, "", "bad_value"]: self.mock_issue.storypoints = bad_sp d._update_github_project_fields( - mock_client, self.mock_downstream, self.mock_issue, - github_project_fields, self.mock_config) + mock_client, + self.mock_downstream, + self.mock_issue, + github_project_fields, + self.mock_config, + ) self.mock_downstream.update.assert_not_called() mock_client.add_comment.assert_not_called() - @mock.patch('jira.client.JIRA') + @mock.patch("jira.client.JIRA") def test_update_github_project_fields_priority(self, mock_client): """ This function tests `_update_github_project_fields` with priority value. """ github_project_fields = { - "priority": { - "gh_field": "Priority", - "options": { - "P0": "Blocker", - "P1": "Critical", - "P2": "Major", - "P3": "Minor", - "P4": "Optional", - "P5": "Trivial" - }}} - d._update_github_project_fields(mock_client, self.mock_downstream, self.mock_issue, - github_project_fields, self.mock_config) - self.mock_downstream.update.assert_called_with({'priority': {'name': 'Critical'}}) - - @mock.patch('jira.client.JIRA') + "priority": { + "gh_field": "Priority", + "options": { + "P0": "Blocker", + "P1": "Critical", + "P2": "Major", + "P3": "Minor", + "P4": "Optional", + "P5": "Trivial", + }, + } + } + d._update_github_project_fields( + mock_client, + self.mock_downstream, + self.mock_issue, + github_project_fields, + self.mock_config, + ) + self.mock_downstream.update.assert_called_with( + {"priority": {"name": "Critical"}} + ) + + @mock.patch("jira.client.JIRA") def test_update_github_project_fields_priority_bad(self, mock_client): """This function tests `_update_github_project_fields` with a bad priority value. @@ -1734,12 +1679,18 @@ def test_update_github_project_fields_priority_bad(self, mock_client): "P2": "Major", "P3": "Minor", "P4": "Optional", - "P5": "Trivial" - }}} - for bad_pv in [None, '', 'bad_value']: + "P5": "Trivial", + }, + } + } + for bad_pv in [None, "", "bad_value"]: self.mock_issue.priority = bad_pv d._update_github_project_fields( - mock_client, self.mock_downstream, self.mock_issue, - github_project_fields, self.mock_config) + mock_client, + self.mock_downstream, + self.mock_issue, + github_project_fields, + self.mock_config, + ) self.mock_downstream.update.assert_not_called() mock_client.add_comment.assert_not_called() diff --git a/tests/test_downstream_pr.py b/tests/test_downstream_pr.py index 3471053a..3264a1a9 100644 --- a/tests/test_downstream_pr.py +++ b/tests/test_downstream_pr.py @@ -4,7 +4,7 @@ import sync2jira.downstream_pr as d -PATH = 'sync2jira.downstream_pr.' +PATH = "sync2jira.downstream_pr." class TestDownstreamPR(unittest.TestCase): @@ -17,48 +17,51 @@ def setUp(self): Setting up the testing environment """ self.mock_pr = MagicMock() - self.mock_pr.jira_key = 'JIRA-1234' - self.mock_pr.suffix = 'mock_suffix' - self.mock_pr.title = 'mock_title' - self.mock_pr.url = 'mock_url' - self.mock_pr.reporter = 'mock_reporter' - self.mock_pr.downstream = {'pr_updates': [ - {'merge_transition': 'CUSTOM_TRANSITION1'}, - {'link_transition': 'CUSTOM_TRANSITION2'}, - ]} + self.mock_pr.jira_key = "JIRA-1234" + self.mock_pr.suffix = "mock_suffix" + self.mock_pr.title = "mock_title" + self.mock_pr.url = "mock_url" + self.mock_pr.reporter = "mock_reporter" + self.mock_pr.downstream = { + "pr_updates": [ + {"merge_transition": "CUSTOM_TRANSITION1"}, + {"link_transition": "CUSTOM_TRANSITION2"}, + ] + } self.mock_config = { - 'sync2jira': { - 'default_jira_instance': 'another_jira_instance', - 'jira_username': 'mock_user', - 'jira': { - 'mock_jira_instance': {'mock_jira': 'mock_jira'}, - 'another_jira_instance': {'token_auth': 'mock_token', - 'options': {'server': 'mock_server'}} + "sync2jira": { + "default_jira_instance": "another_jira_instance", + "jira_username": "mock_user", + "jira": { + "mock_jira_instance": {"mock_jira": "mock_jira"}, + "another_jira_instance": { + "token_auth": "mock_token", + "options": {"server": "mock_server"}, + }, }, - 'testing': False, - 'legacy_matching': False, - 'admins': [{'mock_admin': 'mock_email'}], - 'develop': False + "testing": False, + "legacy_matching": False, + "admins": [{"mock_admin": "mock_email"}], + "develop": False, }, } self.mock_client = MagicMock() mock_user = MagicMock() - mock_user.displayName = 'mock_reporter' - mock_user.key = 'mock_key' + mock_user.displayName = "mock_reporter" + mock_user.key = "mock_key" self.mock_client.search_users.return_value = [mock_user] - self.mock_client.search_issues.return_value = ['mock_existing'] + self.mock_client.search_issues.return_value = ["mock_existing"] self.mock_existing = MagicMock() - @mock.patch(PATH + 'update_jira_issue') + @mock.patch(PATH + "update_jira_issue") @mock.patch(PATH + "d_issue") @mock.patch(PATH + "update_transition") - def test_sync_with_jira_link(self, - mock_update_transition, - mock_d_issue, - mock_update_jira_issue): + def test_sync_with_jira_link( + self, mock_update_transition, mock_d_issue, mock_update_jira_issue + ): """ This function tests 'sync_with_jira' """ @@ -69,41 +72,46 @@ def test_sync_with_jira_link(self, d.sync_with_jira(self.mock_pr, self.mock_config) # Assert everything was called correctly - mock_update_jira_issue.assert_called_with('mock_existing', self.mock_pr, self.mock_client) - self.mock_client.search_issues.assert_called_with('Key = JIRA-1234') + mock_update_jira_issue.assert_called_with( + "mock_existing", self.mock_pr, self.mock_client + ) + self.mock_client.search_issues.assert_called_with("Key = JIRA-1234") mock_d_issue.get_jira_client.assert_called_with(self.mock_pr, self.mock_config) - mock_update_transition.mock.asset_called_with(self.mock_client, 'mock_existing', self.mock_pr, 'link_transition') + mock_update_transition.mock.asset_called_with( + self.mock_client, "mock_existing", self.mock_pr, "link_transition" + ) - @mock.patch(PATH + 'update_jira_issue') + @mock.patch(PATH + "update_jira_issue") @mock.patch(PATH + "d_issue") @mock.patch(PATH + "update_transition") - def test_sync_with_jira_merged(self, - mock_update_transition, - mock_d_issue, - mock_update_jira_issue): + def test_sync_with_jira_merged( + self, mock_update_transition, mock_d_issue, mock_update_jira_issue + ): """ This function tests 'sync_with_jira' """ # Set up return values mock_client = MagicMock() - mock_client.search_issues.return_value = ['mock_existing'] + mock_client.search_issues.return_value = ["mock_existing"] mock_d_issue.get_jira_client.return_value = mock_client - self.mock_pr.suffix = 'merged' + self.mock_pr.suffix = "merged" # Call the function d.sync_with_jira(self.mock_pr, self.mock_config) # Assert everything was called correctly - mock_update_jira_issue.assert_called_with('mock_existing', self.mock_pr, mock_client) - mock_client.search_issues.assert_called_with('Key = JIRA-1234') + mock_update_jira_issue.assert_called_with( + "mock_existing", self.mock_pr, mock_client + ) + mock_client.search_issues.assert_called_with("Key = JIRA-1234") mock_d_issue.get_jira_client.assert_called_with(self.mock_pr, self.mock_config) - mock_update_transition.mock.asset_called_with(mock_client, 'mock_existing', self.mock_pr, 'merged_transition') + mock_update_transition.mock.asset_called_with( + mock_client, "mock_existing", self.mock_pr, "merged_transition" + ) - @mock.patch(PATH + 'update_jira_issue') + @mock.patch(PATH + "update_jira_issue") @mock.patch(PATH + "d_issue") - def test_sync_with_jira_no_issues_found(self, - mock_d_issue, - mock_update_jira_issue): + def test_sync_with_jira_no_issues_found(self, mock_d_issue, mock_update_jira_issue): """ This function tests 'sync_with_jira' where no issues are found """ @@ -116,21 +124,19 @@ def test_sync_with_jira_no_issues_found(self, # Assert everything was called correctly mock_update_jira_issue.assert_not_called() - self.mock_client.search_issues.assert_called_with('Key = JIRA-1234') + self.mock_client.search_issues.assert_called_with("Key = JIRA-1234") mock_d_issue.get_jira_client.assert_called_with(self.mock_pr, self.mock_config) - @mock.patch(PATH + 'update_jira_issue') + @mock.patch(PATH + "update_jira_issue") @mock.patch(PATH + "d_issue") - def test_sync_with_jira_testing(self, - mock_d_issue, - mock_update_jira_issue): + def test_sync_with_jira_testing(self, mock_d_issue, mock_update_jira_issue): """ This function tests 'sync_with_jira' where no issues are found """ # Set up return values mock_client = MagicMock() mock_client.search_issues.return_value = [] - self.mock_config['sync2jira']['testing'] = True + self.mock_config["sync2jira"]["testing"] = True mock_d_issue.get_jira_client.return_value = mock_client # Call the function @@ -141,31 +147,43 @@ def test_sync_with_jira_testing(self, mock_client.search_issues.assert_not_called() mock_d_issue.get_jira_client.assert_not_called() - @mock.patch(PATH + 'comment_exists') - @mock.patch(PATH + 'format_comment') - @mock.patch(PATH + 'd_issue.attach_link') - @mock.patch(PATH + 'issue_link_exists') - def test_update_jira_issue_link(self, - mock_issue_link_exists, - mock_attach_link, - mock_format_comment, - mock_comment_exists): + @mock.patch(PATH + "comment_exists") + @mock.patch(PATH + "format_comment") + @mock.patch(PATH + "d_issue.attach_link") + @mock.patch(PATH + "issue_link_exists") + def test_update_jira_issue_link( + self, + mock_issue_link_exists, + mock_attach_link, + mock_format_comment, + mock_comment_exists, + ): """ This function tests 'update_jira_issue' """ # Set up return values - mock_format_comment.return_value = 'mock_formatted_comment' + mock_format_comment.return_value = "mock_formatted_comment" mock_comment_exists.return_value = False mock_issue_link_exists.return_value = False # Call the function - d.update_jira_issue('mock_existing', self.mock_pr, self.mock_client) + d.update_jira_issue("mock_existing", self.mock_pr, self.mock_client) # Assert everything was called correctly - self.mock_client.add_comment.assert_called_with('mock_existing', 'mock_formatted_comment') - mock_format_comment.assert_called_with(self.mock_pr, self.mock_pr.suffix, self.mock_client) - mock_comment_exists.assert_called_with(self.mock_client, 'mock_existing', 'mock_formatted_comment') - mock_attach_link.assert_called_with(self.mock_client, 'mock_existing', {'url': 'mock_url', 'title': '[PR] mock_title'}) + self.mock_client.add_comment.assert_called_with( + "mock_existing", "mock_formatted_comment" + ) + mock_format_comment.assert_called_with( + self.mock_pr, self.mock_pr.suffix, self.mock_client + ) + mock_comment_exists.assert_called_with( + self.mock_client, "mock_existing", "mock_formatted_comment" + ) + mock_attach_link.assert_called_with( + self.mock_client, + "mock_existing", + {"url": "mock_url", "title": "[PR] mock_title"}, + ) def test_issue_link_exists_false(self): """ @@ -173,7 +191,7 @@ def test_issue_link_exists_false(self): """ # Set up return values mock_issue_link = MagicMock() - mock_issue_link.object.url = 'bad_url' + mock_issue_link.object.url = "bad_url" self.mock_client.remote_links.return_value = [mock_issue_link] # Call the function @@ -199,34 +217,40 @@ def test_issue_link_exists_true(self): self.mock_client.remote_links.assert_called_with(self.mock_existing) self.assertEqual(ret, True) - @mock.patch(PATH + 'format_comment') - @mock.patch(PATH + 'comment_exists') - @mock.patch(PATH + 'd_issue.attach_link') - @mock.patch(PATH + 'issue_link_exists') - def test_update_jira_issue_exists(self, - mock_issue_link_exists, - mock_attach_link, - mock_comment_exists, - mock_format_comment, - ): + @mock.patch(PATH + "format_comment") + @mock.patch(PATH + "comment_exists") + @mock.patch(PATH + "d_issue.attach_link") + @mock.patch(PATH + "issue_link_exists") + def test_update_jira_issue_exists( + self, + mock_issue_link_exists, + mock_attach_link, + mock_comment_exists, + mock_format_comment, + ): """ This function tests 'update_jira_issue' where the comment already exists """ # Set up return values - mock_format_comment.return_value = 'mock_formatted_comment' + mock_format_comment.return_value = "mock_formatted_comment" mock_comment_exists.return_value = True mock_issue_link_exists.return_value = True # Call the function - d.update_jira_issue('mock_existing', self.mock_pr, self.mock_client) + d.update_jira_issue("mock_existing", self.mock_pr, self.mock_client) # Assert everything was called correctly self.mock_client.add_comment.assert_not_called() - mock_format_comment.assert_called_with(self.mock_pr, self.mock_pr.suffix, self.mock_client) - mock_comment_exists.assert_called_with(self.mock_client, 'mock_existing', 'mock_formatted_comment') + mock_format_comment.assert_called_with( + self.mock_pr, self.mock_pr.suffix, self.mock_client + ) + mock_comment_exists.assert_called_with( + self.mock_client, "mock_existing", "mock_formatted_comment" + ) mock_attach_link.assert_not_called() - mock_issue_link_exists.assert_called_with(self.mock_client, 'mock_existing', self.mock_pr) - + mock_issue_link_exists.assert_called_with( + self.mock_client, "mock_existing", self.mock_pr + ) def test_comment_exists_false(self): """ @@ -234,14 +258,16 @@ def test_comment_exists_false(self): """ # Set up return values mock_comment = MagicMock() - mock_comment.body = 'not_mock_new_comment' + mock_comment.body = "not_mock_new_comment" self.mock_client.comments.return_value = [mock_comment] # Call the function - response = d.comment_exists(self.mock_client, 'mock_existing', 'mock_new_comment') + response = d.comment_exists( + self.mock_client, "mock_existing", "mock_new_comment" + ) # Assert Everything was called correctly - self.mock_client.comments.assert_called_with('mock_existing') + self.mock_client.comments.assert_called_with("mock_existing") self.assertEqual(response, False) def test_comment_exists_true(self): @@ -250,14 +276,16 @@ def test_comment_exists_true(self): """ # Set up return values mock_comment = MagicMock() - mock_comment.body = 'mock_new_comment' + mock_comment.body = "mock_new_comment" self.mock_client.comments.return_value = [mock_comment] # Call the function - response = d.comment_exists(self.mock_client, 'mock_existing', 'mock_new_comment') + response = d.comment_exists( + self.mock_client, "mock_existing", "mock_new_comment" + ) # Assert Everything was called correctly - self.mock_client.comments.assert_called_with('mock_existing') + self.mock_client.comments.assert_called_with("mock_existing") self.assertEqual(response, True) def test_format_comment_closed(self): @@ -265,7 +293,7 @@ def test_format_comment_closed(self): This function tests 'format_comment' where the PR is closed """ # Call the function - response = d.format_comment(self.mock_pr, 'closed', self.mock_client) + response = d.format_comment(self.mock_pr, "closed", self.mock_client) # Assert Everything was called correctly self.assertEqual(response, "Merge request [mock_title| mock_url] was closed.") @@ -275,7 +303,7 @@ def test_format_comment_reopened(self): This function tests 'format_comment' where the PR is reopened """ # Call the function - response = d.format_comment(self.mock_pr, 'reopened', self.mock_client) + response = d.format_comment(self.mock_pr, "reopened", self.mock_client) # Assert Everything was called correctly self.assertEqual(response, "Merge request [mock_title| mock_url] was reopened.") @@ -285,7 +313,7 @@ def test_format_comment_merged(self): This function tests 'format_comment' where the PR is merged """ # Call the function - response = d.format_comment(self.mock_pr, 'merged', self.mock_client) + response = d.format_comment(self.mock_pr, "merged", self.mock_client) # Assert Everything was called correctly self.assertEqual(response, "Merge request [mock_title| mock_url] was merged!") @@ -295,11 +323,13 @@ def test_format_comment_open(self): This function tests 'format_comment' where the PR is open """ # Call the function - response = d.format_comment(self.mock_pr, 'open', self.mock_client) + response = d.format_comment(self.mock_pr, "open", self.mock_client) # Assert Everything was called correctly - self.assertEqual(response, "[~mock_key] mentioned this issue in merge request [mock_title| mock_url].") - + self.assertEqual( + response, + "[~mock_key] mentioned this issue in merge request [mock_title| mock_url].", + ) def test_format_comment_open_no_user_found(self): """ @@ -309,14 +339,16 @@ def test_format_comment_open_no_user_found(self): self.mock_client.search_users.return_value = [] # Call the function - response = d.format_comment(self.mock_pr, 'open', self.mock_client) + response = d.format_comment(self.mock_pr, "open", self.mock_client) # Assert Everything was called correctly - self.assertEqual(response, "mock_reporter mentioned this issue in merge request [mock_title| mock_url].") + self.assertEqual( + response, + "mock_reporter mentioned this issue in merge request [mock_title| mock_url].", + ) - @mock.patch(PATH + 'd_issue') - def test_update_transition(self, - mock_d_issue): + @mock.patch(PATH + "d_issue") + def test_update_transition(self, mock_d_issue): """ This function tests 'update_transition' """ @@ -324,7 +356,11 @@ def test_update_transition(self, mock_client = MagicMock() # Call the function - d.update_transition(mock_client, self.mock_existing, self.mock_pr, 'merge_transition') + d.update_transition( + mock_client, self.mock_existing, self.mock_pr, "merge_transition" + ) # Assert everything was called correctly - mock_d_issue.change_status.assert_called_with(mock_client, self.mock_existing, 'CUSTOM_TRANSITION1', self.mock_pr) + mock_d_issue.change_status.assert_called_with( + mock_client, self.mock_existing, "CUSTOM_TRANSITION1", self.mock_pr + ) diff --git a/tests/test_intermediary.py b/tests/test_intermediary.py index 654a38ca..62b52179 100644 --- a/tests/test_intermediary.py +++ b/tests/test_intermediary.py @@ -3,80 +3,92 @@ import sync2jira.intermediary as i -PATH = 'sync2jira.intermediary.' +PATH = "sync2jira.intermediary." class TestIntermediary(unittest.TestCase): """ This class tests the downstream_issue.py file under sync2jira """ + def setUp(self): self.mock_config = { - 'sync2jira': { - 'map': { - 'github': { - 'github': {'mock_downstream': 'mock_key'} - } - } + "sync2jira": { + "map": {"github": {"github": {"mock_downstream": "mock_key"}}} } } self.mock_github_issue = { - 'comments': [{ - 'author': 'mock_author', - 'name': 'mock_name', - 'body': 'mock_body', - 'id': 'mock_id', - 'date_created': 'mock_date' - }], - 'title': 'mock_title', - 'html_url': 'mock_url', - 'id': 1234, - 'labels': 'mock_tags', - 'milestone': 'mock_milestone', - 'priority': 'mock_priority', - 'body': 'mock_content', - 'user': 'mock_reporter', - 'assignees': 'mock_assignee', - 'state': 'open', - 'date_created': 'mock_date', - 'number': '1', - 'storypoints': 'mock_storypoints', + "comments": [ + { + "author": "mock_author", + "name": "mock_name", + "body": "mock_body", + "id": "mock_id", + "date_created": "mock_date", + } + ], + "title": "mock_title", + "html_url": "mock_url", + "id": 1234, + "labels": "mock_tags", + "milestone": "mock_milestone", + "priority": "mock_priority", + "body": "mock_content", + "user": "mock_reporter", + "assignees": "mock_assignee", + "state": "open", + "date_created": "mock_date", + "number": "1", + "storypoints": "mock_storypoints", } self.mock_github_pr = { - 'comments': [{ - 'author': 'mock_author', - 'name': 'mock_name', - 'body': 'mock_body', - 'id': 'mock_id', - 'date_created': 'mock_date' - }], - 'title': 'mock_title', - 'html_url': 'mock_url', - 'id': 1234, - 'labels': 'mock_tags', - 'milestone': 'mock_milestone', - 'priority': 'mock_priority', - 'body': 'mock_content', - 'user': {'fullname': 'mock_reporter'}, - 'assignee': 'mock_assignee', - 'state': 'open', - 'date_created': 'mock_date', - 'number': 1234, + "comments": [ + { + "author": "mock_author", + "name": "mock_name", + "body": "mock_body", + "id": "mock_id", + "date_created": "mock_date", + } + ], + "title": "mock_title", + "html_url": "mock_url", + "id": 1234, + "labels": "mock_tags", + "milestone": "mock_milestone", + "priority": "mock_priority", + "body": "mock_content", + "user": {"fullname": "mock_reporter"}, + "assignee": "mock_assignee", + "state": "open", + "date_created": "mock_date", + "number": 1234, } def checkResponseFields(self, response): - self.assertEqual(response.source, 'github') - self.assertEqual(response.title, '[github] mock_title') - self.assertEqual(response.url, 'mock_url') - self.assertEqual(response.upstream, 'github') - self.assertEqual(response.comments, [{'body': 'mock_body', 'name': 'mock_name', 'author': 'mock_author', - 'changed': None, 'date_created': 'mock_date', 'id': 'mock_id'}]) - self.assertEqual(response.content, 'mock_content') - self.assertEqual(response.reporter, 'mock_reporter') - self.assertEqual(response.assignee, 'mock_assignee') - self.assertEqual(response.id, '1234') + self.assertEqual(response.source, "github") + self.assertEqual(response.title, "[github] mock_title") + self.assertEqual(response.url, "mock_url") + self.assertEqual(response.upstream, "github") + self.assertEqual( + response.comments, + [ + { + "body": "mock_body", + "name": "mock_name", + "author": "mock_author", + "changed": None, + "date_created": "mock_date", + "id": "mock_id", + } + ], + ) + self.assertEqual(response.content, "mock_content") + self.assertEqual(response.reporter, "mock_reporter") + self.assertEqual(response.assignee, "mock_assignee") + self.assertEqual(response.id, "1234") def test_from_github_open(self): """ @@ -84,19 +96,17 @@ def test_from_github_open(self): """ # Call the function response = i.Issue.from_github( - upstream='github', - issue=self.mock_github_issue, - config=self.mock_config + upstream="github", issue=self.mock_github_issue, config=self.mock_config ) # Assert that we made the calls correctly self.checkResponseFields(response) - self.assertEqual(response.fixVersion, ['mock_milestone']) - self.assertEqual(response.priority, 'mock_priority') - self.assertEqual(response.status, 'Open') - self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) - self.assertEqual(response.storypoints, 'mock_storypoints') + self.assertEqual(response.fixVersion, ["mock_milestone"]) + self.assertEqual(response.priority, "mock_priority") + self.assertEqual(response.status, "Open") + self.assertEqual(response.downstream, {"mock_downstream": "mock_key"}) + self.assertEqual(response.storypoints, "mock_storypoints") def test_from_github_open_without_priority(self): """ @@ -104,98 +114,93 @@ def test_from_github_open_without_priority(self): where the state is open but the priority is not initialized. """ mock_github_issue = { - 'comments': [{ - 'author': 'mock_author', - 'name': 'mock_name', - 'body': 'mock_body', - 'id': 'mock_id', - 'date_created': 'mock_date' - }], - 'title': 'mock_title', - 'html_url': 'mock_url', - 'id': 1234, - 'labels': 'mock_tags', - 'milestone': 'mock_milestone', - 'body': 'mock_content', - 'user': 'mock_reporter', - 'assignees': 'mock_assignee', - 'state': 'open', - 'date_created': 'mock_date', - 'number': '1', - 'storypoints': 'mock_storypoints', + "comments": [ + { + "author": "mock_author", + "name": "mock_name", + "body": "mock_body", + "id": "mock_id", + "date_created": "mock_date", + } + ], + "title": "mock_title", + "html_url": "mock_url", + "id": 1234, + "labels": "mock_tags", + "milestone": "mock_milestone", + "body": "mock_content", + "user": "mock_reporter", + "assignees": "mock_assignee", + "state": "open", + "date_created": "mock_date", + "number": "1", + "storypoints": "mock_storypoints", } # Call the function response = i.Issue.from_github( - upstream='github', - issue=mock_github_issue, - config=self.mock_config + upstream="github", issue=mock_github_issue, config=self.mock_config ) # Assert that we made the calls correctly self.checkResponseFields(response) self.assertEqual(response.priority, None) - self.assertEqual(response.status, 'Open') - + self.assertEqual(response.status, "Open") def test_from_github_closed(self): """ This tests the 'from_github' function under the Issue class where the state is closed """ # Set up return values - self.mock_github_issue['state'] = 'closed' + self.mock_github_issue["state"] = "closed" # Call the function response = i.Issue.from_github( - upstream='github', - issue=self.mock_github_issue, - config=self.mock_config + upstream="github", issue=self.mock_github_issue, config=self.mock_config ) # Assert that we made the calls correctly self.checkResponseFields(response) - self.assertEqual(response.tags, 'mock_tags') - self.assertEqual(response.fixVersion, ['mock_milestone']) - self.assertEqual(response.priority, 'mock_priority') - self.assertEqual(response.status, 'Closed') - self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) - self.assertEqual(response.storypoints, 'mock_storypoints') + self.assertEqual(response.tags, "mock_tags") + self.assertEqual(response.fixVersion, ["mock_milestone"]) + self.assertEqual(response.priority, "mock_priority") + self.assertEqual(response.status, "Closed") + self.assertEqual(response.downstream, {"mock_downstream": "mock_key"}) + self.assertEqual(response.storypoints, "mock_storypoints") def test_mapping_github(self): """ This tests the mapping feature from GitHub """ # Set up return values - self.mock_config['sync2jira']['map']['github']['github'] = { - 'mock_downstream': 'mock_key', - 'mapping': [{'fixVersion': 'Test XXX'}] + self.mock_config["sync2jira"]["map"]["github"]["github"] = { + "mock_downstream": "mock_key", + "mapping": [{"fixVersion": "Test XXX"}], } - self.mock_github_issue['state'] = 'closed' + self.mock_github_issue["state"] = "closed" # Call the function response = i.Issue.from_github( - upstream='github', - issue=self.mock_github_issue, - config=self.mock_config + upstream="github", issue=self.mock_github_issue, config=self.mock_config ) # Assert that we made the calls correctly self.checkResponseFields(response) - self.assertEqual(response.tags, 'mock_tags') - self.assertEqual(response.fixVersion, ['Test mock_milestone']) - self.assertEqual(response.priority, 'mock_priority') - self.assertEqual(response.status, 'Closed') - self.assertEqual(response.downstream, { - 'mock_downstream': 'mock_key', - 'mapping': [{'fixVersion': 'Test XXX'}]}) - self.assertEqual(response.storypoints, 'mock_storypoints') - - @mock.patch(PATH + 'matcher') - def test_from_github_pr_reopen(self, - mock_matcher): + self.assertEqual(response.tags, "mock_tags") + self.assertEqual(response.fixVersion, ["Test mock_milestone"]) + self.assertEqual(response.priority, "mock_priority") + self.assertEqual(response.status, "Closed") + self.assertEqual( + response.downstream, + {"mock_downstream": "mock_key", "mapping": [{"fixVersion": "Test XXX"}]}, + ) + self.assertEqual(response.storypoints, "mock_storypoints") + + @mock.patch(PATH + "matcher") + def test_from_github_pr_reopen(self, mock_matcher): """ This tests the message from GitHub for a PR """ @@ -204,24 +209,26 @@ def test_from_github_pr_reopen(self, # Call the function response = i.PR.from_github( - upstream='github', + upstream="github", pr=self.mock_github_pr, - suffix='reopened', - config=self.mock_config + suffix="reopened", + config=self.mock_config, ) # Assert that we made the calls correctly self.checkResponseFields(response) - self.assertEqual(response.suffix, 'reopened') + self.assertEqual(response.suffix, "reopened") self.assertEqual(response.status, None) - self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) + self.assertEqual(response.downstream, {"mock_downstream": "mock_key"}) self.assertEqual(response.jira_key, "JIRA-1234") - self.mock_github_pr['comments'][0]['changed'] = None - mock_matcher.assert_called_with(self.mock_github_pr['body'], self.mock_github_pr['comments']) + self.mock_github_pr["comments"][0]["changed"] = None + mock_matcher.assert_called_with( + self.mock_github_pr["body"], self.mock_github_pr["comments"] + ) def test_matcher(self): - """ This tests the matcher function """ + """This tests the matcher function""" # Positive case content = "Relates to JIRA: XYZ-5678" comments = [{"body": "Relates to JIRA: ABC-1234"}] diff --git a/tests/test_main.py b/tests/test_main.py index dd146714..c55d0696 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -4,39 +4,33 @@ import sync2jira.main as m -PATH = 'sync2jira.main.' +PATH = "sync2jira.main." class TestMain(unittest.TestCase): """ This class tests the main.py file under sync2jira """ + def setUp(self): """ Set up the testing environment """ # Mock Config dict self.mock_config = { - 'sync2jira': { - 'jira': { - 'mock_jira_instance': {'mock_jira': 'mock_jira'} - }, - 'testing': {}, - 'legacy_matching': False, - 'map': { - 'github': {'key_github': {'sync': ['issue', 'pullrequest']}} - }, - 'initialize': True, - 'listen': True, - 'develop': False, + "sync2jira": { + "jira": {"mock_jira_instance": {"mock_jira": "mock_jira"}}, + "testing": {}, + "legacy_matching": False, + "map": {"github": {"key_github": {"sync": ["issue", "pullrequest"]}}}, + "initialize": True, + "listen": True, + "develop": False, }, } # Mock Fedmsg Message - self.mock_message = { - 'msg_id': 'mock_id', - 'msg': {'issue': 'mock_issue'} - } + self.mock_message = {"msg_id": "mock_id", "msg": {"issue": "mock_issue"}} def _check_for_exception(self, loader, target, exc=ValueError): try: @@ -47,56 +41,53 @@ def _check_for_exception(self, loader, target, exc=ValueError): def test_config_validate_empty(self): loader = lambda: {} - self._check_for_exception(loader, 'No sync2jira section') + self._check_for_exception(loader, "No sync2jira section") def test_config_validate_missing_map(self): - loader = lambda: {'sync2jira': {}} - self._check_for_exception(loader, 'No sync2jira.map section') + loader = lambda: {"sync2jira": {}} + self._check_for_exception(loader, "No sync2jira.map section") def test_config_validate_misspelled_mappings(self): - loader = lambda: {'sync2jira': {'map': {'githob': {}}}, 'jira': {}} + loader = lambda: {"sync2jira": {"map": {"githob": {}}}, "jira": {}} self._check_for_exception(loader, 'Specified handlers: "githob", must') def test_config_validate_missing_jira(self): - loader = lambda: {'sync2jira': {'map': {'github': {}}}} - self._check_for_exception(loader, 'No sync2jira.jira section') + loader = lambda: {"sync2jira": {"map": {"github": {}}}} + self._check_for_exception(loader, "No sync2jira.jira section") def test_config_validate_all_good(self): - loader = lambda: {'sync2jira': {'map': {'github': {}}, 'jira': {}}} + loader = lambda: {"sync2jira": {"map": {"github": {}}, "jira": {}}} m.load_config(loader) # Should succeed without an exception. - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - @mock.patch(PATH + 'load_config') - def test_close_duplicates(self, - mock_load_config, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + @mock.patch(PATH + "load_config") + def test_close_duplicates(self, mock_load_config, mock_d, mock_u): """ This tests the 'close_duplicates' function where everything goes smoothly """ # Set up return values mock_load_config.return_value = self.mock_config - mock_u.github_issues.return_value = ['mock_issue_github'] + mock_u.github_issues.return_value = ["mock_issue_github"] # Call the function m.close_duplicates() # Assert everything was called correctly mock_load_config.assert_called_once() - mock_u.github_issues.assert_called_with('key_github', self.mock_config) - mock_d.close_duplicates.assert_any_call('mock_issue_github', self.mock_config) + mock_u.github_issues.assert_called_with("key_github", self.mock_config) + mock_d.close_duplicates.assert_any_call("mock_issue_github", self.mock_config) - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - @mock.patch(PATH + 'load_config') + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + @mock.patch(PATH + "load_config") def test_close_duplicates_errors(self, mock_load_config, mock_d, mock_u): """ This tests the 'close_duplicates' function where closing duplicates raises an exception """ # Set up return values mock_load_config.return_value = self.mock_config - mock_u.github_issues.return_value = ['mock_issue'] + mock_u.github_issues.return_value = ["mock_issue"] mock_d.close_duplicates.side_effect = Exception() # Call the function @@ -106,13 +97,11 @@ def test_close_duplicates_errors(self, mock_load_config, mock_d, mock_u): # Assert everything was called correctly mock_load_config.assert_called_once() mock_u.github_issues.assert_called_once() - mock_d.close_duplicates.assert_called_with('mock_issue', self.mock_config) + mock_d.close_duplicates.assert_called_with("mock_issue", self.mock_config) - @mock.patch(PATH + 'load_config') - @mock.patch(PATH + 'u_issue') - def test_list_managed(self, - mock_u, - mock_load_config): + @mock.patch(PATH + "load_config") + @mock.patch(PATH + "u_issue") + def test_list_managed(self, mock_u, mock_load_config): """ This tests the 'list_managed' function """ @@ -124,22 +113,24 @@ def test_list_managed(self, # Assert everything was called correctly mock_load_config.assert_called_once() - mock_u.github_issues.assert_called_with('key_github', self.mock_config) - - @mock.patch(PATH + 'initialize_recent') - @mock.patch(PATH + 'report_failure') - @mock.patch(PATH + 'INITIALIZE', 1) - @mock.patch(PATH + 'initialize_issues') - @mock.patch(PATH + 'initialize_pr') - @mock.patch(PATH + 'load_config') - @mock.patch(PATH + 'listen') - def test_main_initialize(self, - mock_listen, - mock_load_config, - mock_initialize_pr, - mock_initialize_issues, - mock_report_failure, - mock_initialize_recent): + mock_u.github_issues.assert_called_with("key_github", self.mock_config) + + @mock.patch(PATH + "initialize_recent") + @mock.patch(PATH + "report_failure") + @mock.patch(PATH + "INITIALIZE", 1) + @mock.patch(PATH + "initialize_issues") + @mock.patch(PATH + "initialize_pr") + @mock.patch(PATH + "load_config") + @mock.patch(PATH + "listen") + def test_main_initialize( + self, + mock_listen, + mock_load_config, + mock_initialize_pr, + mock_initialize_issues, + mock_report_failure, + mock_initialize_recent, + ): """ This tests the 'main' function """ @@ -158,20 +149,22 @@ def test_main_initialize(self, mock_report_failure.assert_not_called() mock_initialize_recent.assert_not_called() - @mock.patch(PATH + 'initialize_recent') - @mock.patch(PATH + 'report_failure') - @mock.patch(PATH + 'INITIALIZE', 0) - @mock.patch(PATH + 'initialize_issues') - @mock.patch(PATH + 'initialize_pr') - @mock.patch(PATH + 'load_config') - @mock.patch(PATH + 'listen') - def test_main_no_initialize(self, - mock_listen, - mock_load_config, - mock_initialize_pr, - mock_initialize_issues, - mock_report_failure, - mock_initialize_recent): + @mock.patch(PATH + "initialize_recent") + @mock.patch(PATH + "report_failure") + @mock.patch(PATH + "INITIALIZE", 0) + @mock.patch(PATH + "initialize_issues") + @mock.patch(PATH + "initialize_pr") + @mock.patch(PATH + "load_config") + @mock.patch(PATH + "listen") + def test_main_no_initialize( + self, + mock_listen, + mock_load_config, + mock_initialize_pr, + mock_initialize_issues, + mock_report_failure, + mock_initialize_recent, + ): """ This tests the 'main' function """ @@ -190,52 +183,46 @@ def test_main_no_initialize(self, mock_report_failure.assert_not_called() mock_initialize_recent.assert_called_with(self.mock_config) - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_initialize(self, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_initialize(self, mock_d, mock_u): """ This tests 'initialize' function where everything goes smoothly! """ # Set up return values - mock_u.github_issues.return_value = ['mock_issue_github'] + mock_u.github_issues.return_value = ["mock_issue_github"] # Call the function m.initialize_issues(self.mock_config) # Assert everything was called correctly - mock_u.github_issues.assert_called_with('key_github', self.mock_config) - mock_d.sync_with_jira.assert_any_call('mock_issue_github', self.mock_config) + mock_u.github_issues.assert_called_with("key_github", self.mock_config) + mock_d.sync_with_jira.assert_any_call("mock_issue_github", self.mock_config) - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_initialize_repo_name_github(self, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_initialize_repo_name_github(self, mock_d, mock_u): """ This tests 'initialize' function where we want to sync an individual repo for GitHub """ # Set up return values - mock_u.github_issues.return_value = ['mock_issue_github'] + mock_u.github_issues.return_value = ["mock_issue_github"] # Call the function - m.initialize_issues(self.mock_config, repo_name='key_github') + m.initialize_issues(self.mock_config, repo_name="key_github") # Assert everything was called correctly - mock_u.github_issues.assert_called_with('key_github', self.mock_config) - mock_d.sync_with_jira.assert_called_with('mock_issue_github', self.mock_config) + mock_u.github_issues.assert_called_with("key_github", self.mock_config) + mock_d.sync_with_jira.assert_called_with("mock_issue_github", self.mock_config) - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_initialize_errors(self, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_initialize_errors(self, mock_d, mock_u): """ This tests 'initialize' function where syncing with JIRA throws an exception """ # Set up return values - mock_u.github_issues.return_value = ['mock_issue_github'] + mock_u.github_issues.return_value = ["mock_issue_github"] mock_d.sync_with_jira.side_effect = Exception() # Call the function @@ -243,48 +230,44 @@ def test_initialize_errors(self, m.initialize_issues(self.mock_config) # Assert everything was called correctly - mock_u.github_issues.assert_called_with('key_github', self.mock_config) - mock_d.sync_with_jira.assert_any_call('mock_issue_github', self.mock_config) - - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - @mock.patch(PATH + 'sleep') - @mock.patch(PATH + 'report_failure') - def test_initialize_api_limit(self, - mock_report_failure, - mock_sleep, - mock_d, - mock_u): + mock_u.github_issues.assert_called_with("key_github", self.mock_config) + mock_d.sync_with_jira.assert_any_call("mock_issue_github", self.mock_config) + + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + @mock.patch(PATH + "sleep") + @mock.patch(PATH + "report_failure") + def test_initialize_api_limit( + self, mock_report_failure, mock_sleep, mock_d, mock_u + ): """ This tests 'initialize' where we get an GitHub API limit error. """ # Set up return values - mock_error = MagicMock(side_effect=Exception('API rate limit exceeded')) + mock_error = MagicMock(side_effect=Exception("API rate limit exceeded")) mock_u.github_issues.side_effect = mock_error # Call the function m.initialize_issues(self.mock_config, testing=True) # Assert everything was called correctly - mock_u.github_issues.assert_called_with('key_github', self.mock_config) + mock_u.github_issues.assert_called_with("key_github", self.mock_config) mock_d.sync_with_jira.assert_not_called() mock_sleep.assert_called_with(3600) mock_report_failure.assert_not_called() - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - @mock.patch(PATH + 'sleep') - @mock.patch(PATH + 'report_failure') - def test_initialize_github_error(self, - mock_report_failure, - mock_sleep, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + @mock.patch(PATH + "sleep") + @mock.patch(PATH + "report_failure") + def test_initialize_github_error( + self, mock_report_failure, mock_sleep, mock_d, mock_u + ): """ This tests 'initialize' where we get a GitHub API (not limit) error. """ # Set up return values - mock_error = MagicMock(side_effect=Exception('Random Error')) + mock_error = MagicMock(side_effect=Exception("Random Error")) mock_u.github_issues.side_effect = mock_error # Call the function @@ -292,21 +275,21 @@ def test_initialize_github_error(self, m.initialize_issues(self.mock_config, testing=True) # Assert everything was called correctly - mock_u.github_issues.assert_called_with('key_github', self.mock_config) + mock_u.github_issues.assert_called_with("key_github", self.mock_config) mock_d.sync_with_jira.assert_not_called() mock_sleep.assert_not_called() mock_report_failure.assert_called_with(self.mock_config) - @mock.patch(PATH + 'handle_msg') - @mock.patch(PATH + 'fedmsg') - def test_listen_no_handlers(self, - mock_fedmsg, - mock_handle_msg): + @mock.patch(PATH + "handle_msg") + @mock.patch(PATH + "fedmsg") + def test_listen_no_handlers(self, mock_fedmsg, mock_handle_msg): """ Test 'listen' function where suffix is not in handlers """ # Set up return values - mock_fedmsg.tail_messages.return_value = [("dummy", "dummy", "mock_topic", self.mock_message)] + mock_fedmsg.tail_messages.return_value = [ + ("dummy", "dummy", "mock_topic", self.mock_message) + ] # Call the function m.listen(self.mock_config) @@ -314,19 +297,18 @@ def test_listen_no_handlers(self, # Assert everything was called correctly mock_handle_msg.assert_not_called() - @mock.patch(PATH + 'handle_msg') - @mock.patch(PATH + 'issue_handlers') - @mock.patch(PATH + 'fedmsg') - def test_listen_no_issue(self, - mock_fedmsg, - mock_handlers_issue, - mock_handle_msg): + @mock.patch(PATH + "handle_msg") + @mock.patch(PATH + "issue_handlers") + @mock.patch(PATH + "fedmsg") + def test_listen_no_issue(self, mock_fedmsg, mock_handlers_issue, mock_handle_msg): """ Test 'listen' function where the handler returns none """ # Set up return values - mock_handlers_issue['github.issue.comment'].return_value = None - mock_fedmsg.tail_messages.return_value = [("dummy", "dummy", "d.d.d.github.issue.drop", self.mock_message)] + mock_handlers_issue["github.issue.comment"].return_value = None + mock_fedmsg.tail_messages.return_value = [ + ("dummy", "dummy", "d.d.d.github.issue.drop", self.mock_message) + ] # Call the function m.listen(self.mock_config) @@ -334,33 +316,30 @@ def test_listen_no_issue(self, # Assert everything was called correctly mock_handle_msg.assert_not_called() - @mock.patch(PATH + 'handle_msg') - @mock.patch(PATH + 'issue_handlers') - @mock.patch(PATH + 'fedmsg') - def test_listen(self, - mock_fedmsg, - mock_handlers_issue, - mock_handle_msg): + @mock.patch(PATH + "handle_msg") + @mock.patch(PATH + "issue_handlers") + @mock.patch(PATH + "fedmsg") + def test_listen(self, mock_fedmsg, mock_handlers_issue, mock_handle_msg): """ Test 'listen' function where everything goes smoothly """ # Set up return values - mock_handlers_issue['github.issue.comment'].return_value = 'dummy_issue' - mock_fedmsg.tail_messages.return_value = [("dummy", "dummy", "d.d.d.github.issue.comment", self.mock_message)] + mock_handlers_issue["github.issue.comment"].return_value = "dummy_issue" + mock_fedmsg.tail_messages.return_value = [ + ("dummy", "dummy", "d.d.d.github.issue.comment", self.mock_message) + ] # Call the function m.listen(self.mock_config) # Assert everything was called correctly mock_handle_msg.assert_called_with( - self.mock_message, - 'github.issue.comment', self.mock_config) + self.mock_message, "github.issue.comment", self.mock_config + ) - @mock.patch(PATH + 'send_mail') - @mock.patch(PATH + 'jinja2') - def test_report_failure(self, - mock_jinja2, - mock_send_mail): + @mock.patch(PATH + "send_mail") + @mock.patch(PATH + "jinja2") + def test_report_failure(self, mock_jinja2, mock_send_mail): """ Tests 'report_failure' function """ @@ -368,110 +347,96 @@ def test_report_failure(self, mock_template_loader = MagicMock() mock_template_env = MagicMock() mock_template = MagicMock() - mock_template.render.return_value = 'mock_html' + mock_template.render.return_value = "mock_html" mock_template_env.get_template.return_value = mock_template mock_jinja2.FileSystemLoader.return_value = mock_template_loader mock_jinja2.Environment.return_value = mock_template_env # Call the function - m.report_failure({'sync2jira': {'mailing-list': 'mock_email'}}) + m.report_failure({"sync2jira": {"mailing-list": "mock_email"}}) # Assert everything was called correctly - mock_send_mail.assert_called_with(cc=None, - recipients=['mock_email'], - subject='Sync2Jira Has Failed!', - text='mock_html') + mock_send_mail.assert_called_with( + cc=None, + recipients=["mock_email"], + subject="Sync2Jira Has Failed!", + text="mock_html", + ) - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_handle_msg_no_handlers(self, - mock_d, - mock_u): + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_handle_msg_no_handlers(self, mock_d, mock_u): """ Tests 'handle_msg' function where there are no handlers """ # Call the function - m.handle_msg(self.mock_message, 'no_handler', self.mock_config) + m.handle_msg(self.mock_message, "no_handler", self.mock_config) # Assert everything was called correctly mock_d.sync_with_jira.assert_not_called() mock_u.handle_github_message.assert_not_called() - @mock.patch(PATH + 'issue_handlers') - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_handle_msg_no_issue(self, - mock_d, - mock_u, - mock_handlers_issue): + @mock.patch(PATH + "issue_handlers") + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_handle_msg_no_issue(self, mock_d, mock_u, mock_handlers_issue): """ Tests 'handle_msg' function where there is no issue """ # Set up return values - mock_handlers_issue['github.issue.comment'].return_value = None + mock_handlers_issue["github.issue.comment"].return_value = None # Call the function - m.handle_msg(self.mock_message, 'github.issue.comment', self.mock_config) + m.handle_msg(self.mock_message, "github.issue.comment", self.mock_config) # Assert everything was called correctly mock_d.sync_with_jira.assert_not_called() mock_u.handle_github_message.assert_not_called() - @mock.patch(PATH + 'issue_handlers') - @mock.patch(PATH + 'u_issue') - @mock.patch(PATH + 'd_issue') - def test_handle_msg(self, - mock_d, - mock_u, - mock_handlers_issue): + @mock.patch(PATH + "issue_handlers") + @mock.patch(PATH + "u_issue") + @mock.patch(PATH + "d_issue") + def test_handle_msg(self, mock_d, mock_u, mock_handlers_issue): """ Tests 'handle_msg' function """ # Set up return values - mock_handlers_issue['github.issue.comment'].return_value = 'dummy_issue' - mock_u.handle_github_message.return_value = 'dummy_issue' + mock_handlers_issue["github.issue.comment"].return_value = "dummy_issue" + mock_u.handle_github_message.return_value = "dummy_issue" # Call the function - m.handle_msg(self.mock_message, 'github.issue.comment', self.mock_config) + m.handle_msg(self.mock_message, "github.issue.comment", self.mock_config) # Assert everything was called correctly - mock_d.sync_with_jira.assert_called_with('dummy_issue', self.mock_config) + mock_d.sync_with_jira.assert_called_with("dummy_issue", self.mock_config) - @mock.patch(PATH + 'handle_msg') - @mock.patch(PATH + 'query') - def test_initialize_recent(self, - mock_query, - mock_handle_msg): + @mock.patch(PATH + "handle_msg") + @mock.patch(PATH + "query") + def test_initialize_recent(self, mock_query, mock_handle_msg): """ Tests 'initialize_recent' function """ # Set up return values - mock_query.return_value = [{ - 'topic': 'm.m.m.github.issue.comment', - 'msg': 'mock_msg' - - }] + mock_query.return_value = [ + {"topic": "m.m.m.github.issue.comment", "msg": "mock_msg"} + ] # Call the function m.initialize_recent(self.mock_config) # Assert everything was called correctly - mock_handle_msg.assert_called_with({'msg': 'mock_msg'}, 'github.issue.comment', self.mock_config) + mock_handle_msg.assert_called_with( + {"msg": "mock_msg"}, "github.issue.comment", self.mock_config + ) - @mock.patch(PATH + 'handle_msg') - @mock.patch(PATH + 'query') - def test_initialize_recent_no_handler(self, - mock_query, - mock_handle_msg): + @mock.patch(PATH + "handle_msg") + @mock.patch(PATH + "query") + def test_initialize_recent_no_handler(self, mock_query, mock_handle_msg): """ Tests 'initialize_recent' function where the topic is not for a valid handler """ # Set up return values - mock_query.return_value = [{ - 'topic': 'm.m.m.bad.topic', - 'msg': 'mock_msg' - - }] + mock_query.return_value = [{"topic": "m.m.m.bad.topic", "msg": "mock_msg"}] # Call the function m.initialize_recent(self.mock_config) @@ -479,46 +444,40 @@ def test_initialize_recent_no_handler(self, # Assert everything was called correctly mock_handle_msg.assert_not_called() - @mock.patch(PATH + 'get') - def test_query(self, - mock_get): + @mock.patch(PATH + "get") + def test_query(self, mock_get): """ Tests 'query' function """ # Set up return values - mock_get.return_value = { - 'raw_messages': ['test_msg'], - 'count': 1, - 'total': 1 - } + mock_get.return_value = {"raw_messages": ["test_msg"], "count": 1, "total": 1} # Call the function response = list(m.query()) # Assert everything was called correctly mock_get.assert_called_once() - self.assertEqual(mock_get.call_args.kwargs['params']['order'], 'asc') - self.assertEqual(response, ['test_msg']) + self.assertEqual(mock_get.call_args.kwargs["params"]["order"], "asc") + self.assertEqual(response, ["test_msg"]) - @mock.patch(PATH + 'HTTPKerberosAuth') - @mock.patch(PATH + 'requests') - def test_get(self, - mock_requests, - mock_kerberos_auth): + @mock.patch(PATH + "HTTPKerberosAuth") + @mock.patch(PATH + "requests") + def test_get(self, mock_requests, mock_kerberos_auth): """ Tests 'get' function """ # Set up return values mock_response = MagicMock() - mock_response.json.return_value = 'mock_return_value' + mock_response.json.return_value = "mock_return_value" mock_requests.get.return_value = mock_response # Call the function - response = m.get('mock_params') + response = m.get("mock_params") # Assert everything was called correctly - self.assertEqual(response, 'mock_return_value') + self.assertEqual(response, "mock_return_value") mock_requests.get.assert_called_with( auth=mock_kerberos_auth(), - headers={'Accept': 'application/json'}, - params='mock_params', - url=m.DATAGREPPER_URL) + headers={"Accept": "application/json"}, + params="mock_params", + url=m.DATAGREPPER_URL, + ) diff --git a/tests/test_upstream_issue.py b/tests/test_upstream_issue.py index e1c71a36..8753de46 100644 --- a/tests/test_upstream_issue.py +++ b/tests/test_upstream_issue.py @@ -5,61 +5,52 @@ import sync2jira.upstream_issue as u -PATH = 'sync2jira.upstream_issue.' +PATH = "sync2jira.upstream_issue." class TestUpstreamIssue(unittest.TestCase): """ This class tests the upstream_issue.py file under sync2jira """ + def setUp(self): self.mock_config = { - 'sync2jira': { - 'map': { - 'github': { - 'org/repo': {'sync': ['issue']} - }, + "sync2jira": { + "map": { + "github": {"org/repo": {"sync": ["issue"]}}, }, - 'jira': { + "jira": { # Nothing, really.. }, - 'filters': { - 'github': - {'org/repo': {'filter1': 'filter1', 'labels': ['custom_tag']}}, + "filters": { + "github": { + "org/repo": {"filter1": "filter1", "labels": ["custom_tag"]} + }, }, - 'github_token': 'mock_token' + "github_token": "mock_token", }, } # Mock GitHub Comment self.mock_github_comment = MagicMock() - self.mock_github_comment.user.name = 'mock_username' - self.mock_github_comment.body = 'mock_body' - self.mock_github_comment.id = 'mock_id' - self.mock_github_comment.created_at = 'mock_created_at' + self.mock_github_comment.user.name = "mock_username" + self.mock_github_comment.body = "mock_body" + self.mock_github_comment.id = "mock_id" + self.mock_github_comment.created_at = "mock_created_at" # Mock GitHub Message self.mock_github_message = { - 'msg': { - 'repository': { - 'owner': { - 'login': 'org' - }, - 'name': 'repo' + "msg": { + "repository": {"owner": {"login": "org"}, "name": "repo"}, + "issue": { + "filter1": "filter1", + "labels": [{"name": "custom_tag"}], + "comments": ["some_comments!"], + "number": "mock_number", + "user": {"login": "mock_login"}, + "assignees": [{"login": "mock_login"}], + "milestone": {"title": "mock_milestone"}, }, - 'issue': { - 'filter1': 'filter1', - 'labels': [{'name': 'custom_tag'}], - 'comments': ['some_comments!'], - 'number': 'mock_number', - 'user': { - 'login': 'mock_login' - }, - 'assignees': [{'login': 'mock_login'}], - 'milestone': { - 'title': 'mock_milestone' - } - } } } @@ -69,21 +60,17 @@ def setUp(self): # Mock GitHub Issue Raw self.mock_github_issue_raw = { - 'comments': ['some comment'], - 'number': '1234', - 'user': { - 'login': 'mock_login' - }, - 'assignees': [{'login': 'mock_assignee_login'}], - 'labels': [{'name': 'some_label'}], - 'milestone': { - 'title': 'mock_milestone' - } + "comments": ["some comment"], + "number": "1234", + "user": {"login": "mock_login"}, + "assignees": [{"login": "mock_assignee_login"}], + "labels": [{"name": "some_label"}], + "milestone": {"title": "mock_milestone"}, } # Mock GitHub Reporter self.mock_github_person = MagicMock() - self.mock_github_person.name = 'mock_name' + self.mock_github_person.name = "mock_name" # Mock GitHub Repo self.mock_github_repo = MagicMock() @@ -94,377 +81,412 @@ def setUp(self): self.mock_github_client.get_repo.return_value = self.mock_github_repo self.mock_github_client.get_user.return_value = self.mock_github_person - @mock.patch('sync2jira.intermediary.Issue.from_github') - @mock.patch(PATH + 'requests.post') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'get_all_github_data') - def test_github_issues(self, - mock_get_all_github_data, - mock_github, - mock_requests_post, - mock_issue_from_github): + @mock.patch("sync2jira.intermediary.Issue.from_github") + @mock.patch(PATH + "requests.post") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "get_all_github_data") + def test_github_issues( + self, + mock_get_all_github_data, + mock_github, + mock_requests_post, + mock_issue_from_github, + ): """ This function tests 'github_issues' function """ # Set up return values mock_github.return_value = self.mock_github_client mock_get_all_github_data.return_value = [self.mock_github_issue_raw] - mock_issue_from_github.return_value = 'Successful Call!' + mock_issue_from_github.return_value = "Successful Call!" mock_requests_post.return_value.status_code = 200 - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_number'] = 1 + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_number" + ] = 1 # Call the function - response = list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + response = list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that calls were made correctly try: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1", + {"Authorization": "token mock_token"}, ) except AssertionError: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag", + {"Authorization": "token mock_token"}, ) - self.mock_github_client.get_user.assert_any_call('mock_login') - self.mock_github_client.get_user.assert_any_call('mock_assignee_login') + self.mock_github_client.get_user.assert_any_call("mock_login") + self.mock_github_client.get_user.assert_any_call("mock_assignee_login") mock_issue_from_github.assert_called_with( - 'org/repo', + "org/repo", { - 'labels': ['some_label'], - 'number': '1234', - 'comments': [ + "labels": ["some_label"], + "number": "1234", + "comments": [ { - 'body': 'mock_body', - 'name': unittest.mock.ANY, - 'author': 'mock_username', - 'changed': None, - 'date_created': 'mock_created_at', - 'id': 'mock_id'} + "body": "mock_body", + "name": unittest.mock.ANY, + "author": "mock_username", + "changed": None, + "date_created": "mock_created_at", + "id": "mock_id", + } ], - 'assignees': [{'fullname': 'mock_name'}], - 'user': {'login': 'mock_login', 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone'}, - self.mock_config + "assignees": [{"fullname": "mock_name"}], + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + }, + self.mock_config, ) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_issue.assert_called_with(number='1234') + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_issue.assert_called_with(number="1234") self.mock_github_issue.get_comments.assert_any_call() - self.assertEqual(response[0], 'Successful Call!') - - @mock.patch('sync2jira.intermediary.Issue.from_github') - @mock.patch(PATH + 'requests.post') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'get_all_github_data') - def test_github_issues_with_storypoints(self, - mock_get_all_github_data, - mock_github, - mock_requests_post, - mock_issue_from_github): + self.assertEqual(response[0], "Successful Call!") + + @mock.patch("sync2jira.intermediary.Issue.from_github") + @mock.patch(PATH + "requests.post") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "get_all_github_data") + def test_github_issues_with_storypoints( + self, + mock_get_all_github_data, + mock_github, + mock_requests_post, + mock_issue_from_github, + ): """ This function tests 'github_issues' function with story points """ - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_number'] = 1 - self.mock_config['sync2jira']['map']['github']['org/repo']['issue_updates'] = ['github_project_fields'] - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_fields'] = { - "storypoints": { - "gh_field": "Estimate" - }, + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_number" + ] = 1 + self.mock_config["sync2jira"]["map"]["github"]["org/repo"]["issue_updates"] = [ + "github_project_fields" + ] + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_fields" + ] = { + "storypoints": {"gh_field": "Estimate"}, } # Set up return values mock_github.return_value = self.mock_github_client mock_get_all_github_data.return_value = [self.mock_github_issue_raw] - mock_issue_from_github.return_value = 'Successful Call!' + mock_issue_from_github.return_value = "Successful Call!" mock_requests_post.return_value.status_code = 200 mock_requests_post.return_value.json.return_value = { - 'data': { - 'repository': { - 'issue': { - 'projectItems': { - 'nodes': [{'project': {'title': 'Project 1', 'number': 1}, - 'fieldValues': {'nodes': [{'fieldName': {'name': 'Estimate'}, 'number': 2.0}]}}, - {'project': {'title': 'Project 2', 'number': 2}, - 'fieldValues': {'nodes': []}}, - ] - } + "data": { + "repository": { + "issue": { + "projectItems": { + "nodes": [ + { + "project": {"title": "Project 1", "number": 1}, + "fieldValues": { + "nodes": [ + { + "fieldName": {"name": "Estimate"}, + "number": 2.0, + } + ] + }, + }, + { + "project": {"title": "Project 2", "number": 2}, + "fieldValues": {"nodes": []}, + }, + ] } } } } + } # Call the function - response = list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + response = list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that calls were made correctly try: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1", + {"Authorization": "token mock_token"}, ) except AssertionError: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag", + {"Authorization": "token mock_token"}, ) - self.mock_github_client.get_user.assert_any_call('mock_login') - self.mock_github_client.get_user.assert_any_call('mock_assignee_login') + self.mock_github_client.get_user.assert_any_call("mock_login") + self.mock_github_client.get_user.assert_any_call("mock_assignee_login") mock_issue_from_github.assert_called_with( - 'org/repo', + "org/repo", { - 'labels': ['some_label'], - 'number': '1234', - 'comments': [ + "labels": ["some_label"], + "number": "1234", + "comments": [ { - 'body': 'mock_body', - 'name': unittest.mock.ANY, - 'author': 'mock_username', - 'changed': None, - 'date_created': 'mock_created_at', - 'id': 'mock_id'} + "body": "mock_body", + "name": unittest.mock.ANY, + "author": "mock_username", + "changed": None, + "date_created": "mock_created_at", + "id": "mock_id", + } ], - 'assignees': [{'fullname': 'mock_name'}], - 'user': {'login': 'mock_login', 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone', - 'storypoints': 2, - 'priority': None}, - self.mock_config + "assignees": [{"fullname": "mock_name"}], + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + "storypoints": 2, + "priority": None, + }, + self.mock_config, ) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_issue.assert_called_with(number='1234') + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_issue.assert_called_with(number="1234") self.mock_github_issue.get_comments.assert_any_call() - self.assertEqual(response[0], 'Successful Call!') - - @mock.patch('sync2jira.intermediary.Issue.from_github') - @mock.patch(PATH + 'requests.post') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'get_all_github_data') - def test_github_issues_with_priority(self, - mock_get_all_github_data, - mock_github, - mock_requests_post, - mock_issue_from_github): + self.assertEqual(response[0], "Successful Call!") + + @mock.patch("sync2jira.intermediary.Issue.from_github") + @mock.patch(PATH + "requests.post") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "get_all_github_data") + def test_github_issues_with_priority( + self, + mock_get_all_github_data, + mock_github, + mock_requests_post, + mock_issue_from_github, + ): """ This function tests 'github_issues' function with priority """ - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_number'] = 1 - self.mock_config['sync2jira']['map']['github']['org/repo']['issue_updates'] = ['github_project_fields'] - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_fields'] = { + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_number" + ] = 1 + self.mock_config["sync2jira"]["map"]["github"]["org/repo"]["issue_updates"] = [ + "github_project_fields" + ] + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_fields" + ] = { "priority": { "gh_field": "Priority", - "options": { - "P0": "Blocker", - "P1": "Critical", - "P2": "Major", - "P3": "Minor", - "P4": "Optional", - "P5": "Trivial" + "options": { + "P0": "Blocker", + "P1": "Critical", + "P2": "Major", + "P3": "Minor", + "P4": "Optional", + "P5": "Trivial", + }, } - } } # Set up return values mock_github.return_value = self.mock_github_client mock_get_all_github_data.return_value = [self.mock_github_issue_raw] - mock_issue_from_github.return_value = 'Successful Call!' + mock_issue_from_github.return_value = "Successful Call!" mock_requests_post.return_value.status_code = 200 - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_number'] = 1 + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_number" + ] = 1 mock_requests_post.return_value.json.return_value = { - 'data': { - 'repository': { - 'issue': { - 'projectItems': { - 'nodes': [{'project': {'title': 'Project 1', 'number': 1}, - 'fieldValues': {'nodes': [{'fieldName': {'name': 'Priority'}, 'name': 'P1'}]}}, - {'project': {'title': 'Project 2', 'number': 2}, - 'fieldValues': {'nodes': []}}, - ] - } + "data": { + "repository": { + "issue": { + "projectItems": { + "nodes": [ + { + "project": {"title": "Project 1", "number": 1}, + "fieldValues": { + "nodes": [ + { + "fieldName": {"name": "Priority"}, + "name": "P1", + } + ] + }, + }, + { + "project": {"title": "Project 2", "number": 2}, + "fieldValues": {"nodes": []}, + }, + ] } } } } + } # Call the function - response = list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + response = list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that calls were made correctly try: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1", + {"Authorization": "token mock_token"}, ) except AssertionError: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag", + {"Authorization": "token mock_token"}, ) - self.mock_github_client.get_user.assert_any_call('mock_login') - self.mock_github_client.get_user.assert_any_call('mock_assignee_login') + self.mock_github_client.get_user.assert_any_call("mock_login") + self.mock_github_client.get_user.assert_any_call("mock_assignee_login") mock_issue_from_github.assert_called_with( - 'org/repo', + "org/repo", { - 'labels': ['some_label'], - 'number': '1234', - 'comments': [ + "labels": ["some_label"], + "number": "1234", + "comments": [ { - 'body': 'mock_body', - 'name': unittest.mock.ANY, - 'author': 'mock_username', - 'changed': None, - 'date_created': 'mock_created_at', - 'id': 'mock_id'} + "body": "mock_body", + "name": unittest.mock.ANY, + "author": "mock_username", + "changed": None, + "date_created": "mock_created_at", + "id": "mock_id", + } ], - 'assignees': [{'fullname': 'mock_name'}], - 'user': {'login': 'mock_login', 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone', - 'storypoints': None, - 'priority': 'P1'}, - self.mock_config + "assignees": [{"fullname": "mock_name"}], + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + "storypoints": None, + "priority": "P1", + }, + self.mock_config, ) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_issue.assert_called_with(number='1234') + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_issue.assert_called_with(number="1234") self.mock_github_issue.get_comments.assert_any_call() - self.assertEqual(response[0], 'Successful Call!') - - @mock.patch('sync2jira.intermediary.Issue.from_github') - @mock.patch(PATH + 'requests.post') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'get_all_github_data') - def test_github_issues_no_token(self, - mock_get_all_github_data, - mock_github, - mock_requests_post, - mock_issue_from_github): + self.assertEqual(response[0], "Successful Call!") + + @mock.patch("sync2jira.intermediary.Issue.from_github") + @mock.patch(PATH + "requests.post") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "get_all_github_data") + def test_github_issues_no_token( + self, + mock_get_all_github_data, + mock_github, + mock_requests_post, + mock_issue_from_github, + ): """ This function tests 'github_issues' function where we have no GitHub token and no comments """ # Set up return values - self.mock_config['sync2jira']['github_token'] = None - self.mock_github_issue_raw['comments'] = 0 + self.mock_config["sync2jira"]["github_token"] = None + self.mock_github_issue_raw["comments"] = 0 mock_github.return_value = self.mock_github_client mock_get_all_github_data.return_value = [self.mock_github_issue_raw] - mock_issue_from_github.return_value = 'Successful Call!' + mock_issue_from_github.return_value = "Successful Call!" mock_requests_post.return_value.status_code = 200 - self.mock_config['sync2jira']['map']['github']['org/repo']['github_project_number'] = 1 + self.mock_config["sync2jira"]["map"]["github"]["org/repo"][ + "github_project_number" + ] = 1 # Call the function - response = list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + response = list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that calls were made correctly try: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1', - {} + "https://api.github.com/repos/org/repo/issues?labels=custom_tag&filter1=filter1", + {}, ) except AssertionError: mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag', - {} + "https://api.github.com/repos/org/repo/issues?filter1=filter1&labels=custom_tag", + {}, ) - self.mock_github_client.get_user.assert_any_call('mock_login') - self.mock_github_client.get_user.assert_any_call('mock_assignee_login') + self.mock_github_client.get_user.assert_any_call("mock_login") + self.mock_github_client.get_user.assert_any_call("mock_assignee_login") mock_issue_from_github.assert_called_with( - 'org/repo', + "org/repo", { - 'labels': ['some_label'], - 'number': '1234', - 'comments': [], - 'assignees': [{'fullname': 'mock_name'}], - 'user': { - 'login': 'mock_login', - 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone'}, - self.mock_config + "labels": ["some_label"], + "number": "1234", + "comments": [], + "assignees": [{"fullname": "mock_name"}], + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + }, + self.mock_config, ) - self.assertEqual(response[0], 'Successful Call!') + self.assertEqual(response[0], "Successful Call!") self.mock_github_client.get_repo.assert_not_called() self.mock_github_repo.get_issue.assert_not_called() self.mock_github_issue.get_comments.assert_not_called() - @mock.patch('sync2jira.intermediary.Issue.from_github') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'get_all_github_data') - def test_filter_multiple_labels(self, - mock_get_all_github_data, - mock_github, - mock_issue_from_github): + @mock.patch("sync2jira.intermediary.Issue.from_github") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "get_all_github_data") + def test_filter_multiple_labels( + self, mock_get_all_github_data, mock_github, mock_issue_from_github + ): """ This function tests 'github_issues' function with a filter including multiple labels """ # Set up return values - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'].extend(['another_tag', 'and_another']) - mock_github.return_value = self.mock_github_client - mock_issue_from_github.return_value = 'Successful Call!' + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"].extend( + ["another_tag", "and_another"] + ) + mock_github.return_value = self.mock_github_client + mock_issue_from_github.return_value = "Successful Call!" # We mutate the issue object so we need to pass a copy here mock_get_all_github_data.return_value = [deepcopy(self.mock_github_issue_raw)] # Call the function - list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that the labels filter is correct self.assertIn( - 'labels=custom_tag%2Canother_tag%2Cand_another', - mock_get_all_github_data.call_args[0][0] + "labels=custom_tag%2Canother_tag%2Cand_another", + mock_get_all_github_data.call_args[0][0], ) # Assert the config value was not mutated self.assertEqual( - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'], - ['custom_tag','another_tag','and_another'] + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"], + ["custom_tag", "another_tag", "and_another"], ) # Restore the return value to the original object mock_get_all_github_data.return_value = [deepcopy(self.mock_github_issue_raw)] # Call the function again to ensure consistency for subsequent calls - list(u.github_issues( - upstream='org/repo', - config=self.mock_config - )) + list(u.github_issues(upstream="org/repo", config=self.mock_config)) # Assert that the labels filter is correct self.assertIn( - 'labels=custom_tag%2Canother_tag%2Cand_another', - mock_get_all_github_data.call_args[0][0] + "labels=custom_tag%2Canother_tag%2Cand_another", + mock_get_all_github_data.call_args[0][0], ) - # Assert the config value was not mutated + # Assert the config value was not mutated self.assertEqual( - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'], - ['custom_tag','another_tag','and_another'] + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"], + ["custom_tag", "another_tag", "and_another"], ) - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_not_in_mapped(self, - mock_issue_from_github, - mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_not_in_mapped( + self, mock_issue_from_github, mock_github + ): """ This function tests 'handle_github_message' where upstream is not in mapped repos """ # Set up return values - self.mock_github_message['msg']['repository']['owner']['login'] = 'bad_owner' + self.mock_github_message["msg"]["repository"]["owner"]["login"] = "bad_owner" # Call the function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that all calls were made correctly @@ -472,21 +494,20 @@ def test_handle_github_message_not_in_mapped(self, mock_github.assert_not_called() self.assertEqual(None, response) - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_pull_request(self, - mock_issue_from_github, - mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_pull_request( + self, mock_issue_from_github, mock_github + ): """ This function tests 'handle_github_message' the issue is a pull request comment """ # Set up return values - self.mock_github_message['msg']['issue'] = {'pull_request': 'test'} + self.mock_github_message["msg"]["issue"] = {"pull_request": "test"} # Call the function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that all calls were made correctly @@ -494,78 +515,81 @@ def test_handle_github_message_pull_request(self, mock_github.assert_not_called() self.assertEqual(None, response) - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_bad_filter(self, - mock_issue_from_github): + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_bad_filter(self, mock_issue_from_github): """ This function tests 'handle_github_message' where comparing the actual vs. filter does not equate """ # Set up return values - self.mock_github_message['msg']['issue']['filter1'] = 'filter2' + self.mock_github_message["msg"]["issue"]["filter1"] = "filter2" # Call function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that calls were made correctly mock_issue_from_github.assert_not_called() self.assertEqual(None, response) - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_bad_label(self, - mock_issue_from_github): + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_bad_label(self, mock_issue_from_github): """ This function tests 'handle_github_message' where comparing the actual vs. filter does not equate """ # Set up return values - self.mock_github_message['msg']['issue']['labels'] = [{'name': 'bad_label'}] + self.mock_github_message["msg"]["issue"]["labels"] = [{"name": "bad_label"}] # Call function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that calls were made correctly mock_issue_from_github.assert_not_called() self.assertEqual(None, response) - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_no_comments(self, mock_issue_from_github, mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_no_comments( + self, mock_issue_from_github, mock_github + ): """ This function tests 'handle_github_message' where we have no comments """ # Set up return values mock_issue_from_github.return_value = "Successful Call!" mock_github.return_value = self.mock_github_client - self.mock_github_message['msg']['issue']['comments'] = 0 + self.mock_github_message["msg"]["issue"]["comments"] = 0 # Call function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that calls were made correctly - mock_issue_from_github.assert_called_with('org/repo', - {'labels': ['custom_tag'], 'number': 'mock_number', - 'comments': [], 'assignees': [{'fullname': 'mock_name'}], - 'filter1': 'filter1', - 'user': {'login': 'mock_login', 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone'}, - self.mock_config) - mock_github.assert_called_with('mock_token', retry=5) - self.assertEqual('Successful Call!', response) + mock_issue_from_github.assert_called_with( + "org/repo", + { + "labels": ["custom_tag"], + "number": "mock_number", + "comments": [], + "assignees": [{"fullname": "mock_name"}], + "filter1": "filter1", + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + }, + self.mock_config, + ) + mock_github.assert_called_with("mock_token", retry=5) + self.assertEqual("Successful Call!", response) self.mock_github_client.get_repo.assert_not_called() self.mock_github_repo.get_issue.assert_not_called() self.mock_github_issue.get_comments.assert_not_called() - self.mock_github_client.get_user.assert_called_with('mock_login') + self.mock_github_client.get_user.assert_called_with("mock_login") - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_successful(self, - mock_issue_from_github, - mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_successful( + self, mock_issue_from_github, mock_github + ): """ This function tests 'handle_github_message' where everything goes smoothly! """ @@ -575,54 +599,63 @@ def test_handle_github_message_successful(self, # Call function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config + msg=self.mock_github_message, config=self.mock_config ) # Assert that calls were made correctly - mock_issue_from_github.assert_called_with('org/repo', - {'labels': ['custom_tag'], 'number': 'mock_number', - 'comments': [{'body': 'mock_body', 'name': unittest.mock.ANY, - 'author': 'mock_username', 'changed': None, - 'date_created': 'mock_created_at', 'id': 'mock_id'}], - 'assignees': [{'fullname': 'mock_name'}], - 'filter1': 'filter1', 'user': - {'login': 'mock_login', 'fullname': 'mock_name'}, - 'milestone': 'mock_milestone'}, self.mock_config) - mock_github.assert_called_with('mock_token', retry=5) - self.assertEqual('Successful Call!', response) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_issue.assert_called_with(number='mock_number') + mock_issue_from_github.assert_called_with( + "org/repo", + { + "labels": ["custom_tag"], + "number": "mock_number", + "comments": [ + { + "body": "mock_body", + "name": unittest.mock.ANY, + "author": "mock_username", + "changed": None, + "date_created": "mock_created_at", + "id": "mock_id", + } + ], + "assignees": [{"fullname": "mock_name"}], + "filter1": "filter1", + "user": {"login": "mock_login", "fullname": "mock_name"}, + "milestone": "mock_milestone", + }, + self.mock_config, + ) + mock_github.assert_called_with("mock_token", retry=5) + self.assertEqual("Successful Call!", response) + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_issue.assert_called_with(number="mock_number") self.mock_github_issue.get_comments.assert_any_call() - self.mock_github_client.get_user.assert_called_with('mock_login') + self.mock_github_client.get_user.assert_called_with("mock_login") - @mock.patch(PATH + 'api_call_get') - @mock.patch(PATH + '_github_link_field_to_dict') - def test_get_all_github_data(self, - mock_github_link_field_to_dict, - mock_api_call_get): + @mock.patch(PATH + "api_call_get") + @mock.patch(PATH + "_github_link_field_to_dict") + def test_get_all_github_data( + self, mock_github_link_field_to_dict, mock_api_call_get + ): """ This tests the '_get_all_github_data' function """ # Set up return values get_return = MagicMock() - get_return.json.return_value = [{'comments_url': 'mock_comments_url'}] - get_return.headers = {'link': 'mock_link'} + get_return.json.return_value = [{"comments_url": "mock_comments_url"}] + get_return.headers = {"link": "mock_link"} mock_api_call_get.return_value = get_return # Call the function - response = list(u.get_all_github_data( - url='mock_url', - headers='mock_headers' - )) + response = list(u.get_all_github_data(url="mock_url", headers="mock_headers")) # Assert everything was called correctly - mock_api_call_get.assert_any_call('mock_url', headers='mock_headers') - mock_api_call_get.assert_any_call('mock_comments_url', headers='mock_headers') - mock_github_link_field_to_dict.assert_called_with('mock_link') - self.assertEqual('mock_comments_url', response[0]['comments_url']) + mock_api_call_get.assert_any_call("mock_url", headers="mock_headers") + mock_api_call_get.assert_any_call("mock_comments_url", headers="mock_headers") + mock_github_link_field_to_dict.assert_called_with("mock_link") + self.assertEqual("mock_comments_url", response[0]["comments_url"]) - @mock.patch(PATH + 'requests') + @mock.patch(PATH + "requests") def test_api_call_get_error(self, mock_requests): """ Tests the 'api_call_get' function where we raise an IOError @@ -632,25 +665,17 @@ def test_api_call_get_error(self, mock_requests): get_return.__bool__ = mock.Mock(return_value=False) get_return.__nonzero__ = get_return.__bool__ get_return.json.side_effect = Exception() - get_return.text.return_value = { - 'issues': [ - {'assignee': 'mock_assignee'} - ] - - } + get_return.text.return_value = {"issues": [{"assignee": "mock_assignee"}]} mock_requests.get.return_value = get_return # Call the function with self.assertRaises(IOError): - u.api_call_get( - url='mock_url', - headers='mock_headers' - ) + u.api_call_get(url="mock_url", headers="mock_headers") # Assert everything was called correctly - mock_requests.get.assert_called_with('mock_url', headers='mock_headers') + mock_requests.get.assert_called_with("mock_url", headers="mock_headers") - @mock.patch(PATH + 'requests') + @mock.patch(PATH + "requests") def test_api_call_get(self, mock_requests): """ Tests the 'api_call_get' function where everything goes smoothly! @@ -663,10 +688,7 @@ def test_api_call_get(self, mock_requests): # Call the function - response = u.api_call_get( - url='mock_url', - headers='mock_headers' - ) + response = u.api_call_get(url="mock_url", headers="mock_headers") def test_get_current_project_node(self): """This function tests '_get_current_project_node' in a matrix of cases. @@ -676,18 +698,24 @@ def test_get_current_project_node(self): none of the associated projects, and with a project which matches one. """ nodes = [ - {'project': {'number': 1, 'url': 'url1', 'title': 'title1'}}, - {'project': {'number': 2, 'url': 'url2', 'title': 'title2'}}] + {"project": {"number": 1, "url": "url1", "title": "title1"}}, + {"project": {"number": 2, "url": "url2", "title": "title2"}}, + ] projects = [None, 2, 5] for project in projects: - for node_count in range(len(nodes)+1): - gh_issue = {'projectItems': {'nodes': nodes[:node_count]}} + for node_count in range(len(nodes) + 1): + gh_issue = {"projectItems": {"nodes": nodes[:node_count]}} result = u._get_current_project_node( - 'org/repo', project, 'mock_number', gh_issue) + "org/repo", project, "mock_number", gh_issue + ) expected_result = ( - None if node_count == 0 else - (nodes[0] if project is None else None) if node_count == 1 else - nodes[1] if project == 2 else - None) + None + if node_count == 0 + else ( + (nodes[0] if project is None else None) + if node_count == 1 + else nodes[1] if project == 2 else None + ) + ) self.assertEqual(result, expected_result) diff --git a/tests/test_upstream_pr.py b/tests/test_upstream_pr.py index 28e4d56f..114c6a65 100644 --- a/tests/test_upstream_pr.py +++ b/tests/test_upstream_pr.py @@ -5,60 +5,53 @@ import sync2jira.upstream_pr as u -PATH = 'sync2jira.upstream_pr.' +PATH = "sync2jira.upstream_pr." class TestUpstreamPR(unittest.TestCase): """ This class tests the upstream_pr.py file under sync2jira """ + def setUp(self): self.mock_config = { - 'sync2jira': { - 'map': { - 'github': { - 'org/repo': {'sync': ['pullrequest']}, + "sync2jira": { + "map": { + "github": { + "org/repo": {"sync": ["pullrequest"]}, }, }, - 'jira': { + "jira": { # Nothing, really.. }, - 'filters': { - 'github': - {'org/repo': {'filter1': 'filter1', 'labels': ['custom_tag']}}, + "filters": { + "github": { + "org/repo": {"filter1": "filter1", "labels": ["custom_tag"]} + }, }, - 'github_token': 'mock_token' + "github_token": "mock_token", }, } # Mock GitHub Comment self.mock_github_comment = MagicMock() - self.mock_github_comment.user.name = 'mock_username' - self.mock_github_comment.body = 'mock_body' - self.mock_github_comment.id = 'mock_id' - self.mock_github_comment.created_at = 'mock_created_at' + self.mock_github_comment.user.name = "mock_username" + self.mock_github_comment.body = "mock_body" + self.mock_github_comment.id = "mock_id" + self.mock_github_comment.created_at = "mock_created_at" # Mock GitHub Message self.mock_github_message = { - 'msg': { - 'repository': { - 'owner': { - 'login': 'org' - }, - 'name': 'repo' - }, - 'pull_request': { - 'filter1': 'filter1', - 'labels': [{'name': 'custom_tag'}], - 'comments': ['some_comments!'], - 'number': 'mock_number', - 'user': { - 'login': 'mock_login' - }, - 'assignees': [{'login': 'mock_login'}], - 'milestone': { - 'title': 'mock_milestone' - } + "msg": { + "repository": {"owner": {"login": "org"}, "name": "repo"}, + "pull_request": { + "filter1": "filter1", + "labels": [{"name": "custom_tag"}], + "comments": ["some_comments!"], + "number": "mock_number", + "user": {"login": "mock_login"}, + "assignees": [{"login": "mock_login"}], + "milestone": {"title": "mock_milestone"}, }, } } @@ -69,21 +62,17 @@ def setUp(self): # Mock GitHub Issue Raw self.mock_github_issue_raw = { - 'comments': ['some comment'], - 'number': '1234', - 'user': { - 'login': 'mock_login' - }, - 'assignees': [{'login': 'mock_assignee_login'}], - 'labels': [{'name': 'some_label'}], - 'milestone': { - 'title': 'mock_milestone' - } + "comments": ["some comment"], + "number": "1234", + "user": {"login": "mock_login"}, + "assignees": [{"login": "mock_assignee_login"}], + "labels": [{"name": "some_label"}], + "milestone": {"title": "mock_milestone"}, } # Mock GitHub Reporter self.mock_github_person = MagicMock() - self.mock_github_person.name = 'mock_name' + self.mock_github_person.name = "mock_name" # Mock GitHub Repo self.mock_github_repo = MagicMock() @@ -95,11 +84,9 @@ def setUp(self): self.mock_github_client.get_repo.return_value = self.mock_github_repo self.mock_github_client.get_user.return_value = self.mock_github_person - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.PR.from_github') - def test_handle_github_message(self, - mock_pr_from_github, - mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.PR.from_github") + def test_handle_github_message(self, mock_pr_from_github, mock_github): """ This function tests 'handle_github_message' """ @@ -109,46 +96,54 @@ def test_handle_github_message(self, # Call function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config, - suffix='mock_suffix' + msg=self.mock_github_message, config=self.mock_config, suffix="mock_suffix" ) # Assert that calls were made correctly mock_pr_from_github.assert_called_with( - 'org/repo', - {'filter1': 'filter1', 'labels': ['custom_tag'], - 'comments': [{'author': 'mock_username', - 'name': unittest.mock.ANY, - 'body': 'mock_body', 'id': 'mock_id', - 'date_created': 'mock_created_at', - 'changed': None}], 'number': 'mock_number', - 'user': {'login': 'mock_login', 'fullname': 'mock_name'}, - 'assignees': [{'fullname': 'mock_name'}], - 'milestone': 'mock_milestone'}, 'mock_suffix', self.mock_config) - mock_github.assert_called_with('mock_token') - self.assertEqual('Successful Call!', response) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_pull.assert_called_with(number='mock_number') + "org/repo", + { + "filter1": "filter1", + "labels": ["custom_tag"], + "comments": [ + { + "author": "mock_username", + "name": unittest.mock.ANY, + "body": "mock_body", + "id": "mock_id", + "date_created": "mock_created_at", + "changed": None, + } + ], + "number": "mock_number", + "user": {"login": "mock_login", "fullname": "mock_name"}, + "assignees": [{"fullname": "mock_name"}], + "milestone": "mock_milestone", + }, + "mock_suffix", + self.mock_config, + ) + mock_github.assert_called_with("mock_token") + self.assertEqual("Successful Call!", response) + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_pull.assert_called_with(number="mock_number") self.mock_github_pr.get_issue_comments.assert_any_call() - self.mock_github_client.get_user.assert_called_with('mock_login') + self.mock_github_client.get_user.assert_called_with("mock_login") - @mock.patch(PATH + 'Github') - @mock.patch('sync2jira.intermediary.Issue.from_github') - def test_handle_github_message_not_in_mapped(self, - mock_issue_from_github, - mock_github): + @mock.patch(PATH + "Github") + @mock.patch("sync2jira.intermediary.Issue.from_github") + def test_handle_github_message_not_in_mapped( + self, mock_issue_from_github, mock_github + ): """ This function tests 'handle_github_message' where upstream is not in mapped repos """ # Set up return values - self.mock_github_message['msg']['repository']['owner']['login'] = 'bad_owner' + self.mock_github_message["msg"]["repository"]["owner"]["login"] = "bad_owner" # Call the function response = u.handle_github_message( - msg=self.mock_github_message, - config=self.mock_config, - suffix='mock_suffix' + msg=self.mock_github_message, config=self.mock_config, suffix="mock_suffix" ) # Assert that all calls were made correctly @@ -156,102 +151,102 @@ def test_handle_github_message_not_in_mapped(self, mock_github.assert_not_called() self.assertEqual(None, response) - @mock.patch('sync2jira.intermediary.PR.from_github') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'u_issue.get_all_github_data') - def test_github_issues(self, - mock_get_all_github_data, - mock_github, - mock_pr_from_github): + @mock.patch("sync2jira.intermediary.PR.from_github") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "u_issue.get_all_github_data") + def test_github_issues( + self, mock_get_all_github_data, mock_github, mock_pr_from_github + ): """ This function tests 'github_issues' function """ # Set up return values mock_github.return_value = self.mock_github_client mock_get_all_github_data.return_value = [self.mock_github_issue_raw] - mock_pr_from_github.return_value = 'Successful Call!' + mock_pr_from_github.return_value = "Successful Call!" # Call the function - response = list(u.github_prs( - upstream='org/repo', - config=self.mock_config - )) + response = list(u.github_prs(upstream="org/repo", config=self.mock_config)) # Assert that calls were made correctly mock_get_all_github_data.assert_called_with( - 'https://api.github.com/repos/org/repo/pulls?filter1=filter1&labels=custom_tag', - {'Authorization': 'token mock_token'} + "https://api.github.com/repos/org/repo/pulls?filter1=filter1&labels=custom_tag", + {"Authorization": "token mock_token"}, ) - self.mock_github_client.get_user.assert_any_call('mock_login') - self.mock_github_client.get_user.assert_any_call('mock_assignee_login') + self.mock_github_client.get_user.assert_any_call("mock_login") + self.mock_github_client.get_user.assert_any_call("mock_assignee_login") mock_pr_from_github.assert_called_with( - 'org/repo', - {'comments': - [{'author': 'mock_username', 'name': unittest.mock.ANY, - 'body': 'mock_body', 'id': 'mock_id', - 'date_created': 'mock_created_at', 'changed': None}], - 'number': '1234', 'user': - {'login': 'mock_login', 'fullname': 'mock_name'}, - 'assignees': [{'fullname': 'mock_name'}], - 'labels': ['some_label'], 'milestone': 'mock_milestone'}, - 'open', - self.mock_config + "org/repo", + { + "comments": [ + { + "author": "mock_username", + "name": unittest.mock.ANY, + "body": "mock_body", + "id": "mock_id", + "date_created": "mock_created_at", + "changed": None, + } + ], + "number": "1234", + "user": {"login": "mock_login", "fullname": "mock_name"}, + "assignees": [{"fullname": "mock_name"}], + "labels": ["some_label"], + "milestone": "mock_milestone", + }, + "open", + self.mock_config, ) - self.mock_github_client.get_repo.assert_called_with('org/repo') - self.mock_github_repo.get_pull.assert_called_with(number='1234') + self.mock_github_client.get_repo.assert_called_with("org/repo") + self.mock_github_repo.get_pull.assert_called_with(number="1234") self.mock_github_pr.get_issue_comments.assert_any_call() - self.assertEqual(response[0], 'Successful Call!') - - @mock.patch('sync2jira.intermediary.PR.from_github') - @mock.patch(PATH + 'Github') - @mock.patch(PATH + 'u_issue.get_all_github_data') - def test_filter_multiple_labels(self, - mock_get_all_github_data, - mock_github, - mock_issue_from_github): + self.assertEqual(response[0], "Successful Call!") + + @mock.patch("sync2jira.intermediary.PR.from_github") + @mock.patch(PATH + "Github") + @mock.patch(PATH + "u_issue.get_all_github_data") + def test_filter_multiple_labels( + self, mock_get_all_github_data, mock_github, mock_issue_from_github + ): """ This function tests 'github_issues' function with a filter including multiple labels """ # Set up return values - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'].extend(['another_tag', 'and_another']) - mock_github.return_value = self.mock_github_client - mock_issue_from_github.return_value = 'Successful Call!' + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"].extend( + ["another_tag", "and_another"] + ) + mock_github.return_value = self.mock_github_client + mock_issue_from_github.return_value = "Successful Call!" # We mutate the issue object so we need to pass a copy here mock_get_all_github_data.return_value = [deepcopy(self.mock_github_issue_raw)] # Call the function - list(u.github_prs( - upstream='org/repo', - config=self.mock_config - )) + list(u.github_prs(upstream="org/repo", config=self.mock_config)) # Assert that the labels filter is correct self.assertIn( - 'labels=custom_tag%2Canother_tag%2Cand_another', - mock_get_all_github_data.call_args[0][0] + "labels=custom_tag%2Canother_tag%2Cand_another", + mock_get_all_github_data.call_args[0][0], ) # Assert the config value was not mutated self.assertEqual( - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'], - ['custom_tag','another_tag','and_another'] + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"], + ["custom_tag", "another_tag", "and_another"], ) # Restore the return value to the original object mock_get_all_github_data.return_value = [deepcopy(self.mock_github_issue_raw)] # Call the function again to ensure consistency for subsequent calls - list(u.github_prs( - upstream='org/repo', - config=self.mock_config - )) + list(u.github_prs(upstream="org/repo", config=self.mock_config)) # Assert that the labels filter is correct self.assertIn( - 'labels=custom_tag%2Canother_tag%2Cand_another', - mock_get_all_github_data.call_args[0][0] + "labels=custom_tag%2Canother_tag%2Cand_another", + mock_get_all_github_data.call_args[0][0], ) - # Assert the config value was not mutated + # Assert the config value was not mutated self.assertEqual( - self.mock_config['sync2jira']['filters']['github']['org/repo']['labels'], - ['custom_tag','another_tag','and_another'] + self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"], + ["custom_tag", "another_tag", "and_another"], )