diff --git a/mlx/jira_interaction.py b/mlx/jira_interaction.py index d3862d7..c1ffaf5 100644 --- a/mlx/jira_interaction.py +++ b/mlx/jira_interaction.py @@ -67,9 +67,10 @@ def create_unique_issues(item_ids, jira, general_fields, settings, traceability_ traceability_collection) jira_field_id = settings['jira_field_id'] - matches = jira.search_issues("project={} and {} ~ {!r}".format(project_id_or_key, + jira_field_query_value = escape_special_characters(jira_field) + matches = jira.search_issues("project={} and {} ~ '{}'".format(project_id_or_key, jira_field_id, - jira_field)) + jira_field_query_value)) if matches: if settings.get('warn_if_exists', False): LOGGER.warning("Won't create a {} for item {!r} because the Jira API query to check to prevent " @@ -191,3 +192,21 @@ def get_info_from_relationship(item, config_for_parent, traceability_collection) if attr_value: attendees = attr_value.split(',') return attendees, jira_field + + +def escape_special_characters(input_string): + """ Escape special characters to avoid unwanted behavior. + + Note that they are not stored in the index so you cannot search for them. + + Args: + input_string (str): String to escape special characters of + + Returns: + str: Input string that has its special characters escaped + """ + prepared_string = input_string + for special_char in ("\\", "+", "-", "&", "|", "!", "(", ")", "{", "}", "[", "]", "^", "~", "*", "?", ":"): + if special_char in prepared_string: + prepared_string = prepared_string.replace(special_char, "\\" + special_char) + return prepared_string diff --git a/tests/test_jira_interaction.py b/tests/test_jira_interaction.py index 0fb157e..9dfb86c 100644 --- a/tests/test_jira_interaction.py +++ b/tests/test_jira_interaction.py @@ -40,7 +40,7 @@ def setUp(self): self.coll = TraceableCollection() parent = TraceableItem('MEETING-12345_2') action1 = TraceableItem('ACTION-12345_ACTION_1') - action1.caption = 'Caption for action 1' + action1.caption = 'Caption for action 1?' action1.set_content('Description for action 1') action2 = TraceableItem('ACTION-12345_ACTION_2') action2.caption = 'Caption for action 2' @@ -131,7 +131,7 @@ def test_create_jira_issues_unique(self, jira): basic_auth=('my_username', 'my_password'))) self.assertEqual(jira_mock.search_issues.call_args_list, [ - mock.call("project=MLX12345 and summary ~ 'MEETING-12345_2: Caption for action 1'"), + mock.call("project=MLX12345 and summary ~ 'MEETING\\-12345_2\\: Caption for action 1\\?'"), mock.call("project=MLX12345 and summary ~ 'Caption for action 2'"), ]) @@ -140,7 +140,7 @@ def test_create_jira_issues_unique(self, jira): jira_mock.create_issue.call_args_list, [ mock.call( - summary='MEETING-12345_2: Caption for action 1', + summary='MEETING-12345_2: Caption for action 1?', description='Description for action 1', assignee={'name': 'ABC'}, **self.general_fields @@ -187,7 +187,7 @@ def test_notify_watchers(self, jira): [ mock.call( description='Description for action 1', - summary='MEETING-12345_2: Caption for action 1', + summary='MEETING-12345_2: Caption for action 1?', **self.general_fields ), mock.call( @@ -267,7 +267,7 @@ def test_default_project(self, jira): jira_mock.create_issue.call_args_list, [ mock.call( - summary='MEETING-12345_2: Caption for action 1', + summary='MEETING-12345_2: Caption for action 1?', description='Description for action 1', assignee={'name': 'ABC'}, **self.general_fields @@ -322,7 +322,8 @@ def test_tuple_for_relationship_to_parent(self, jira): self.assertEqual(jira_mock.search_issues.call_args_list, [ - mock.call("project=MLX12345 and summary ~ 'ZZZ-TO_BE_PRIORITIZED: Caption for action 1'"), + mock.call("project=MLX12345 and summary ~ " + "'ZZZ\\-TO_BE_PRIORITIZED\\: Caption for action 1\\?'"), mock.call("project=MLX12345 and summary ~ 'Caption for action 2'"), ]) @@ -330,7 +331,7 @@ def test_tuple_for_relationship_to_parent(self, jira): jira_mock.create_issue.call_args_list, [ mock.call( - summary='ZZZ-TO_BE_PRIORITIZED: Caption for action 1', + summary='ZZZ-TO_BE_PRIORITIZED: Caption for action 1?', description='Description for action 1', assignee={'name': 'ABC'}, **self.general_fields @@ -354,7 +355,7 @@ def test_get_info_from_relationship_tuple(self, _): attendees, jira_field = dut.get_info_from_relationship(action1, relationship_to_parent, self.coll) self.assertEqual(attendees, []) - self.assertEqual(jira_field, 'ZZZ-TO_BE_PRIORITIZED: Caption for action 1') + self.assertEqual(jira_field, 'ZZZ-TO_BE_PRIORITIZED: Caption for action 1?') def test_get_info_from_relationship_str(self, _): """ Tests dut.get_info_from_relationship with a config_for_parent parameter as str """ @@ -367,4 +368,4 @@ def test_get_info_from_relationship_str(self, _): attendees, jira_field = dut.get_info_from_relationship(action1, relationship_to_parent, self.coll) self.assertEqual(attendees, ['ABC', ' ZZZ']) - self.assertEqual(jira_field, 'MEETING-12345_2: Caption for action 1') + self.assertEqual(jira_field, 'MEETING-12345_2: Caption for action 1?') diff --git a/tox.ini b/tox.ini index d589a70..dd2f927 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,8 @@ envlist = clean, check, {py37}, +requires = + pip>=20.3.4 [testenv] basepython =