diff --git a/docs/Jira.rst b/docs/Jira.rst new file mode 100644 index 00000000..e5295327 --- /dev/null +++ b/docs/Jira.rst @@ -0,0 +1,60 @@ +Jira Configuration +==================== + +First of all Jira must be enabled by setting the enabled property to true in config.cfg:: + + [Jira] + enabled: true + +The rest of the properties can be configured as: + +token: The OAuth token for the Jira account, can be created in Jira web page > Profile > Personal access tokens + +Project data in order of preference: + +- project_id: The project id, ,can be seen in Jira web page > Administration > Projects > The Edit html anchor href will have a pid= segment with the project id + +- project_key: The shortened project upper case identifier that appears in the issues ids, see example below + +- project_name: Full project name + +- execution_url: The root server URL, see example below + +Note: if the project cannot be located, available project for your user will be printed in toolium.log + +onlyifchanges: true if previous state of the updated issues is similar true will force the update, false will omit it + +summary_prefix: # TODO + +fixversion: The fixversion field, see example below + +labels: List of labels to be added + +comments: A comment to be added in each of the test executions + +build: Inactive field # TODO Pending field value confirmation + +Full example:: + + [Jira] + enabled: true + token: My OAuth token + project_id: 12316620 + project_key: COMMONSRDF + project_name: Apache Commons RDF + execution_url: https://jira.atlassian.com + onlyifchanges: true + summary_prefix: [DEV][QA] + fixversion: 4.12 + labels: [QA, label2, 3rdlabel] + comments: "A new comment for the execution" + +See `https://jira.readthedocs.io`_ for the complete Package documentation. + +Jira API +==================== + +See request module for REST API calls `https://requests.readthedocs.io/en/latest/`_ +And the json parsing module `https://docs.python.org/3/library/json.html`_ +See `https://developer.atlassian.com/server/jira/platform/rest-apis/`_ for the the underlying API introduction. +And `https://docs.atlassian.com/software/jira/docs/api/REST/9.5.0/`_ for the underlying API full documentation. diff --git a/requirements.txt b/requirements.txt index b76d570a..cf5ca6f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ screeninfo~=0.8 lxml~=4.9 Faker~=18.3 phonenumbers~=8.13 +jira==3.2.0 \ No newline at end of file diff --git a/toolium/jira.py b/toolium/jira.py index cf0b8b09..b0bf641f 100644 --- a/toolium/jira.py +++ b/toolium/jira.py @@ -17,13 +17,42 @@ """ import logging +import os import re +from pathlib import Path import requests - +from os import path +from jira import JIRA, Issue +from jira.exceptions import JIRAError from toolium.config_driver import get_error_message_from_exception from toolium.driver_wrappers_pool import DriverWrappersPool +logger = logging.getLogger(__name__) + + +class JiraServer: + + def __init__(self, execution_url, token=None): + global logger + logger = logging.getLogger("Jira.Server") + self.url = execution_url + self._token = token + self.server: JIRA = None + + def __enter__(self): + server_url = self.url + headers = JIRA.DEFAULT_OPTIONS["headers"] + headers["Authorization"] = f"Bearer {self._token}" + logger.info("Starting Jira server...") + self.server = JIRA(server=server_url, options={"headers": headers, 'verify': True}, get_server_info=True) + return self.server + + def __exit__(self, exc_type, exc_val, exc_tb): + self.server.close() + logger.info("Jira server closed//") + + # Dict to save tuples with jira keys, their test status, comments and attachments jira_tests_status = {} @@ -32,11 +61,15 @@ # Jira configuration enabled = None -execution_url = None -summary_prefix = None -labels = None +jiratoken = None +project_id = 0 +project_key = "" +project_name = "" +execution_url = '' +summary_prefix = "" +labels = [] comments = None -fix_version = None +fix_version = "" build = None only_if_changes = None @@ -68,17 +101,29 @@ def modified_test(*args, **kwargs): def save_jira_conf(): """Read Jira configuration from properties file and save it""" - global enabled, execution_url, summary_prefix, labels, comments, fix_version, build, only_if_changes, attachments + global enabled, jiratoken, project_id, project_key, project_name, execution_url, summary_prefix, labels, comments,\ + fix_version, build, only_if_changes, attachments config = DriverWrappersPool.get_default_wrapper().config enabled = config.getboolean_optional('Jira', 'enabled') + jiratoken = config.get_optional('Jira', 'token') + project_id = int(config.get_optional('Jira', 'project_id', 0)) + project_key = config.get_optional('Jira', 'project_key') + project_name = config.get_optional('Jira', 'project_name') execution_url = config.get_optional('Jira', 'execution_url') summary_prefix = config.get_optional('Jira', 'summary_prefix') - labels = config.get_optional('Jira', 'labels') + labels_raw = config.get_optional('Jira', 'labels', "") + labels_raw = labels_raw.replace("[", "").replace("]", "") + for label in labels_raw.split(","): + labels.append(label) comments = config.get_optional('Jira', 'comments') fix_version = config.get_optional('Jira', 'fixversion') build = config.get_optional('Jira', 'build') only_if_changes = config.getboolean_optional('Jira', 'onlyifchanges') attachments = [] + jira_properties = {"enabled": enabled, "execution_url": execution_url, "summary_prefix": summary_prefix, + "labels": labels, "comments": comments, "fix_version": fix_version, "build": build, + "only_if_changes": only_if_changes, "attachments": attachments} + logger.debug("Jira properties read:" + jira_properties.__str__()) def add_attachment(attachment): @@ -88,6 +133,7 @@ def add_attachment(attachment): """ if attachment: attachments.append(attachment) + logger.info("Attachement added from: " + attachment) def add_jira_status(test_key, test_status, test_comment): @@ -102,6 +148,8 @@ def add_jira_status(test_key, test_status, test_comment): if test_key in jira_tests_status: # Merge data with previous test status previous_status = jira_tests_status[test_key] + logger.debug("Found previous data for " + test_key.__str__()) + test_status = 'Pass' if previous_status[1] == 'Pass' and test_status == 'Pass' else 'Fail' if previous_status[2] and test_comment: test_comment = '{}\n{}'.format(previous_status[2], test_comment) @@ -110,6 +158,8 @@ def add_jira_status(test_key, test_status, test_comment): attachments += previous_status[3] # Add or update test status jira_tests_status[test_key] = (test_key, test_status, test_comment, attachments) + elif enabled and not test_key: + logger.error("Status not updated, invalid test key") def change_all_jira_status(): @@ -117,17 +167,86 @@ def change_all_jira_status(): for test_status in jira_tests_status.values(): change_jira_status(*test_status) jira_tests_status.clear() + if enabled: + logger.debug("Update attempt complete, clearing queue") + else: + logger.debug("Jira disabled, upload skipped") + + +def check_jira_args(server: JIRA): + global project_name, project_key, project_id + + project_id, project_key = _check_project_set_ids(server, project_id, project_key, project_name) + _check_fix_version(server, project_key, fix_version) + + +def _check_project_set_ids(server: JIRA, p_id: int, p_key: str, p_name: str): + """ + Checks the provided project identifiers returns the values updated if they were empty + Note that the args are provided in order of precedence to prevent mismatches between them + Args: + server (str): jira server instance + p_id (str): The project id + p_key (str): the key shortcut for the project + p_name (str): Project full name as registered when the project was created + Raises: + ValueError: if the project does not exist; will log available project if any + Returns: + p_id (str): The project id + p_key (str): the key shortcut for the project + + """ + + if p_id: + p_key = server.project(str(p_id)).raw["key"] + elif p_key: + p_id = str(server.project(p_key).raw["id"]) + + elif project_name: + for project in server.projects(): + if project_name == project["name"]: + p_id = project["id"] + p_key = project["key"] + logger.debug(f"Read project info, name:'{p_name}', key:'{p_key}', id:'{p_id}'") + + else: + msg = f"No existe el proyecto especificado name:'{p_name}', key:'{p_key}', id:'{p_id}'" + logger.warning(f"Available projects for your user: '{server.projects()}'") + logger.error(msg) + raise ValueError(msg) + return p_id, p_key -def change_jira_status(test_key, test_status, test_comment, test_attachments): + +def _check_fix_version(server: JIRA, project_key: str, fix_version: str) -> None: + """ + Retrieves the fix_versions for the current project and ensures the one provided is valid + Args: + server (str): jira server instance + project_key (str): project ket (short version of the name) + fix_version (str): fix version that will be checked + Raises: + ValueError: if the fix_version is invalid + """ + available_fixversions = [] + for version in server.project_versions(project_key): + available_fixversions.append(version.raw["name"]) + if fix_version not in available_fixversions: + msg = f"No existe la fix version '{fix_version}'" + logger.warning(f"Available fixversions for project {server.project(project_key)} are {available_fixversions}") + logger.error(msg) + raise ValueError(msg) + + +def change_jira_status(test_key, test_status, test_comment, test_attachments: list, jira_server: JIRA = None): """Update test status in Jira :param test_key: test case key in Jira :param test_status: test case status :param test_comment: test case comments - :param test_attachments: test case attachments + :param test_attachments: list of absolutes paths of test case attachment files + :param jira_server: JIRA server instance to use for the upload if any """ - logger = logging.getLogger(__name__) if not execution_url: logger.warning("Test Case '%s' can not be updated: execution_url is not configured", test_key) @@ -137,30 +256,205 @@ def change_jira_status(test_key, test_status, test_comment, test_attachments): composed_comments = comments if test_comment: composed_comments = '{}\n{}'.format(comments, test_comment) if comments else test_comment + + global labels + # TODO remove global reference payload = {'jiraTestCaseId': test_key, 'jiraStatus': test_status, 'summaryPrefix': summary_prefix, 'labels': labels, 'comments': composed_comments, 'version': fix_version, 'build': build} + if only_if_changes: payload['onlyIfStatusChanges'] = 'true' try: - if test_attachments and len(test_attachments) > 0: - files = dict() - for index in range(len(test_attachments)): - files['attachments{}'.format(index)] = open(test_attachments[index], 'rb') - else: - files = None - response = requests.post(execution_url, data=payload, files=files) - except Exception as e: - logger.warning("Error updating Test Case '%s': %s", test_key, e) + server = jira_server if jira_server else JiraServer(execution_url, jiratoken) + with server as server: + + check_jira_args(server) + + existing_issues = execute_query(server, 'issue = ' + test_key) + if not existing_issues: + logger.warning("Jira Issue not found,...") + return + # TODO issue = new_testcase(server, project_id, summary=scenarioname, description=description) + # test_key = issue.key + + logger.debug("Creating execution for " + test_key) + labels += existing_issues[0].fields.labels + summary = f"{summary_prefix} Execution of {existing_issues[0].fields.summary} {test_key}" + new_execution = create_test_execution(server, test_key, summary, fix_version, labels) + logger.info(f"Created execution {new_execution.key} for test " + test_key) + + if composed_comments: + server.add_comment(new_execution.key, composed_comments) + + # TODO massage payload, labels?? + logger.debug("Update skipped for " + test_key) + # issue.update(fields=payload, jira=server) + + logger.debug("Transitioning " + new_execution.key) + transition(server, new_execution, test_status) + + add_results(server, new_execution.key, test_attachments) + + except (JIRAError, requests.exceptions.ConnectionError) as e: + print_trace(logger, e) + logger.error("Exception while updating Issue '%s': %s", test_key, e) return - if response.status_code >= 400: - logger.warning("Error updating Test Case '%s': [%s] %s", test_key, response.status_code, - get_error_message(response.content)) - else: - logger.debug("%s", response.content.decode().splitlines()[0]) +def execute_query(jira: JIRA, query: str) -> list: + logger = logging.getLogger("Jira.Queries") + + logger.info(f"executing query: {query} ...\n") + existing_issues = jira.search_issues(query) + + issuesfound = "" + for issue in existing_issues: + issuesfound += f'\n{issue} {jira.issue(issue).fields.summary}' + if issuesfound: + logger.info("Found issue/s:" + issuesfound) + return existing_issues -def get_error_message(response_content): + +def search_project_id_from_issue(server: JIRA, issue_id: str) -> int: + """ + Returns the id of the project given an issue identifier + """ + # TODO remove either this or the duplicated set id/key method above + try: + if project_id > 0: + logger.debug("Global project id found") + return project_id + except AttributeError: + for project in server.projects(): + if issue_id.split("-")[0].strip().upper() in project.raw["key"]: + logger.debug(f'Found id {project.raw["id"]} for issue {issue_id}') + return int(project.raw["id"]) + return 0 + + +def create_test_execution(server: JIRA, issueid: str, summary: str, + fix_version: str, labels: list = None, + description: str = " ") -> Issue: + """Creates an execution linked to the TestCase provided""" + + issue_dict = { + 'project': {'id': search_project_id_from_issue(server, issueid)}, + 'assignee': {'name': server.current_user()}, + 'issuetype': {'name': 'Test Case Execution'}, + 'parent': {'key': issueid}, + 'summary': summary, + 'fixVersions': [{'name': fix_version}], + # TODO set priority as config input? + # 'priority': {'name': 'Minor', "id": 10101}, + 'labels': labels, + # 'versions': [{'name': fix_version}], + 'description': description, + # TODO add build field ?? + } + + return server.create_issue(fields=issue_dict) + + +def transition(server: JIRA, issue: Issue, test_status: str): + """ + Transitions the issue to a new state, see issue workflows in the project for available options + param test_status: the new status of the issue + """ + logger.info("Setting new status for " + issue.key + + "from " + issue.fields.status.raw['name'] + " to " + test_status) + try: + server.transition_issue(issue.key, transition=test_status.lower()) + except JIRAError: + # TODO Get available transitions + logger.warning("Available transitions for %s are %s", issue.key, server.transitions(issue.key)) + logger.error("Error transitioning Test Case '%s' to status [%s]", issue.key, test_status) + + return + logger.debug("Transitioned issue to status %s", test_status) + + +def _addscreenshots(jira: JIRA, issueid: str, attachements: list = None): + """ + Attach the screenshots found the report folder to the jira issue provided + param issueid: Full Jira ID + param attachements: Absolute paths of attachments + Raises FileNotFound: if an attachement file is not found + """ + + if not attachements: + attachements = read_screenshot_folder() + + for filepath in attachements: + if os.path.isfile(path.join(filepath)): + _add_screenshot_as_file(jira, issueid, path.join(filepath)) + + logger.info("Screenshots uploaded...") + + +def read_screenshot_folder() -> list: + """ + Reads the screenshot folder and returns a list with the absolute paths to each file found + """ + screenshotspath = path.join(path.dirname(__file__), ".", DriverWrappersPool.screenshots_directory) + logger.debug("Reading screenshot folder " + screenshotspath) + file_list = [] + + if len(os.listdir(screenshotspath)) < 1: + logger.warning("Screenshot folder empty...") + + for filename in os.listdir(screenshotspath): + if os.path.isfile(path.join(screenshotspath, filename)): + file_list.append(path.dirname(path.join(screenshotspath, filename))) + + return file_list + + +def _add_screenshot_as_file(jira: JIRA, issue_id: str, screenshotspath: str): + """ + Adds the given file as an attachement for the provided issue + :param jira: JIRA server to be used for the attachement + :param issue_id: The id of the target isue + :param screenshotspath: Absolute path of the attachment + Raises OSError if the file cannot be opened + """ + with open(os.path.join(screenshotspath), 'rb') as file: + logger.debug("Opened screenshot " + file.name) + jira.add_attachment(issue=issue_id, attachment=file) + logger.info("Attached " + file.name + " into " + issue_id) + + +def _addlogs(jira: JIRA, issueid: str, logpath: Path = None): + """ + Attach the logs in the report folder to the jira issue provided + param issueid Full Jira ID + Raises FileNotFound Error if the log file is not found in reports + """ + if not logpath: + logpath = path.join(path.dirname(__file__), + ".", DriverWrappersPool.screenshots_directory, "..", "..", "toolium.log") + + with open(logpath, 'rb') as file: + logger.debug("Opened log file " + file.name) + jira.add_attachment(issue=issueid, attachment=file) + logger.info("Attached log into " + issueid) + + +def add_results(jira: JIRA, issueid: str, attachements: list = None): + """Adds the results to the execution or the associated test case instead""" + + try: + logger.info("Adding results to issue...") + _addscreenshots(jira, issueid, attachements) if attachements else _addscreenshots(jira, issueid) + _addlogs(jira, issueid) + logger.debug("Results added to issue " + issueid) + + except FileNotFoundError as error: + # TODO catch all jira exception types + print_trace(logger, error) + logger.error("Results not added, exception:" + str(error)) + + +def get_error_message(response_content: str): """Extract error message from the HTTP response :param response_content: HTTP response from test case execution API @@ -170,6 +464,8 @@ def get_error_message(response_content): match = apache_regex.search(response_content) if match: error_message = match.group(1) + logger.debug("Error message extracted from HTTP response with regex:" + apache_regex.__repr__()) + else: local_regex = re.compile(r'.*(.*).*') match = local_regex.search(response_content) @@ -177,4 +473,24 @@ def get_error_message(response_content): error_message = match.group(1) else: error_message = response_content + logger.debug("Error message extracted from HTTP response with regex:" + local_regex.__repr__()) + return error_message + + +def print_trace(logger, e): + # TODO refactor + # TODO move to utils as stand alone + # TODO set custom JiraError for toolium? + import traceback + + trace = traceback.format_tb(e.__traceback__, 30) + final_stack = '' + for count in range(len(trace)): + final_stack += f'Trace stack {count} {trace[count]}' + logger.error(f'Error Trace:\n{final_stack}') + logger.error(f'Error: {e.__class__}') + if hasattr(e, "msg"): + logger.error(f'Error Message: {e.msg}') + else: + logger.error(e.args) diff --git a/toolium/test/test_jira.py b/toolium/test/test_jira.py index 294f9f69..9be65e3a 100644 --- a/toolium/test/test_jira.py +++ b/toolium/test/test_jira.py @@ -15,16 +15,21 @@ See the License for the specific language governing permissions and limitations under the License. """ - +import importlib import os - +import unittest +from tempfile import NamedTemporaryFile +from unittest.mock import ANY import mock import pytest -import requests_mock +from jira import JIRAError +from jira.resources import Version from requests.exceptions import ConnectionError +import toolium from toolium import jira from toolium.driver_wrappers_pool import DriverWrappersPool +from toolium.jira import JiraServer, _check_fix_version @pytest.fixture @@ -39,87 +44,55 @@ def logger(): logger_patch.stop() # Clear jira module configuration - jira.enabled = None - jira.execution_url = None - jira.summary_prefix = None - jira.labels = None - jira.comments = None - jira.fix_version = None - jira.build = None - jira.only_if_changes = None - jira.jira_tests_status.clear() - jira.attachments = [] + importlib.reload(toolium.jira) def test_change_jira_status(logger): - # Test response - response = b"The Test Case Execution 'TOOLIUM-11' has been created\r\n" # Configure jira module jira.enabled = True jira.execution_url = 'http://server/execution_service' - jira.summary_prefix = 'prefix' - jira.labels = 'label1 label2' - jira.comments = 'comment' - jira.fix_version = 'Release 1.0' - jira.build = '453' - jira.only_if_changes = True + jira.logger = mock.MagicMock() + jira.check_jira_args = mock.MagicMock() + jira.add_results = mock.MagicMock() + jira.transition = mock.MagicMock() + jira.execute_query = mock.MagicMock() + jira.execute_query.return_value = [mock.MagicMock()] + + def create_test(*args, **kwargs): + mock_test = mock.MagicMock(key="TOOLIUM-TEST") + return mock_test + jira.create_test_execution = create_test - with requests_mock.mock() as req_mock: - # Configure mock - req_mock.post(jira.execution_url, content=response) - - # Test method - jira.change_jira_status('TOOLIUM-1', 'Pass', None, []) - - # Check requested url - assert jira.execution_url == req_mock.request_history[0].url - for partial_url in ['jiraStatus=Pass', 'jiraTestCaseId=TOOLIUM-1', 'summaryPrefix=prefix', - 'labels=label1+label2', 'comments=comment', 'version=Release+1.0', 'build=453', - 'onlyIfStatusChanges=true']: - assert partial_url in req_mock.request_history[0].text + # Test method + jira.change_jira_status('TOOLIUM-1', 'Pass', None, [], mock.MagicMock()) # Check logging call - logger.debug.assert_called_once_with("%s", "The Test Case Execution 'TOOLIUM-11' has been created") + jira.logger.debug.assert_any_call("Creating execution for TOOLIUM-1") + jira.logger.info.assert_called_with("Created execution TOOLIUM-TEST for test TOOLIUM-1") def test_change_jira_status_attachments(logger): - # Test response - response = b"The Test Case Execution 'TOOLIUM-11' has been created\r\n" # Configure jira module jira.enabled = True jira.execution_url = 'http://server/execution_service' - jira.summary_prefix = 'prefix' - jira.labels = 'label1 label2' - jira.comments = 'comment' - jira.fix_version = 'Release 1.0' - jira.build = '453' jira.only_if_changes = True - resources_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources') - attachments = [os.path.join(resources_path, 'ios.png'), os.path.join(resources_path, 'ios_web.png')] + attachments = [os.path.dirname(os.path.join(NamedTemporaryFile().name))] + jira.logger = mock.MagicMock() - with requests_mock.mock() as req_mock: - # Configure mock - req_mock.post(jira.execution_url, content=response) + def addlogs(*args, **kwargs): + jira.logger.info("Attached log into TOOLIUM-1") + jira._addlogs = addlogs - # Test method - jira.change_jira_status('TOOLIUM-1', 'Pass', None, attachments) - - # Get response body - body_bytes = req_mock.last_request.body - - # Check requested url - body = "".join(map(chr, body_bytes)) - for partial_url in ['"jiraStatus"\r\n\r\nPass', '"jiraTestCaseId"\r\n\r\nTOOLIUM-1', - '"summaryPrefix"\r\n\r\nprefix', '"labels"\r\n\r\nlabel1 label2', - '"comments"\r\n\r\ncomment', '"version"\r\n\r\nRelease 1.0', '"build"\r\n\r\n453', - '"onlyIfStatusChanges"\r\n\r\ntrue', '"attachments0"; filename="ios.png"', - '"attachments1"; filename="ios_web.png"']: - assert partial_url in body + # Test method + jira.add_results(mock.MagicMock(), 'TOOLIUM-1', attachments) # Check logging call - logger.debug.assert_called_once_with("%s", "The Test Case Execution 'TOOLIUM-11' has been created") + calls = [mock.call('Adding results to issue...'), + mock.call('Screenshots uploaded...'), + mock.call("Attached log into TOOLIUM-1")] + jira.logger.info.assert_has_calls(calls) @mock.patch('toolium.jira.requests.get') @@ -129,29 +102,31 @@ def test_change_jira_status_empty_url(jira_get, logger): # Configure jira module jira.enabled = True + jira.logger = mock.MagicMock() # Test method jira.change_jira_status('TOOLIUM-1', 'Pass', None, []) # Check logging error message - logger.warning.assert_called_once_with("Test Case '%s' can not be updated: execution_url is not configured", - 'TOOLIUM-1') + jira.logger.warning.assert_called_once_with( + "Test Case '%s' can not be updated: execution_url is not configured", 'TOOLIUM-1') @mock.patch('toolium.jira.requests.post') def test_change_jira_status_exception(jira_post, logger): # Configure jira mock - jira_post.side_effect = ConnectionError('exception error') + jira_post.side_effect = ConnectionError() # Configure jira module jira.enabled = True jira.execution_url = 'http://server/execution_service' + jira.logger = mock.MagicMock() # Test method jira.change_jira_status('TOOLIUM-1', 'Pass', None, []) # Check logging error message - logger.warning.assert_called_once_with("Error updating Test Case '%s': %s", 'TOOLIUM-1', jira_post.side_effect) + jira.logger.error.assert_called_with("Exception while updating Issue '%s': %s", 'TOOLIUM-1', ANY) def test_jira_annotation_pass(logger): @@ -243,3 +218,53 @@ def mock_test_pass_2(self): @jira.jira(test_key='TOOLIUM-3') def mock_test_fail(self): raise AssertionError('test error') + + +def test_jira_connection_anonymous(): + with JiraServer("https://issues.apache.org/jira", None) as jira: + assert len(jira.projects()) > 1 + + +@unittest.skip("TOKEN injection pending") +def test_jira_connection_private_user(): + def call_jira_server(): + with JiraServer("https://issues.apache.org/jira", "") as jira: # TODO + assert jira.current_user() + unittest.TestCase().assertRaises(JIRAError, call_jira_server) + + +def test_jira_connection_invalid_url(): + # with JiraServer("http://github.com", None) as jira: + # return + def call_jira_server(): + with JiraServer("http://github.com", None): + return + unittest.TestCase().assertRaisesRegex(JIRAError, "^JiraError HTTP 406 url", call_jira_server) + + +def test_jira_connection_invalid_token(): + def call_jira_server(): + with JiraServer("https://jira.elevenpaths.com/", "6") as jira: + assert jira.current_user() + unittest.TestCase().assertRaises(JIRAError, call_jira_server) + + +def test_jira_check_valid_fix_version(): + version = mock.Mock(Version) + version.configure_mock(raw={"name": "1.1"}) + server = mock.Mock(JiraServer("https://jira.elevenpaths.com/", "6")) + server.configure_mock(project=lambda x: "KEY", project_versions=lambda arg: [version]) + _check_fix_version(server, "KEY", "1.1") + + +def test_jira_check_wrong_fix_version(): + version = mock.Mock(Version) + version.configure_mock(raw={"name": "0.5"}) + server = mock.Mock(JiraServer("https://jira.elevenpaths.com/", "6")) + server.configure_mock(project=lambda x: "KEY", project_versions=lambda arg: [version]) + + def call_check_fix_version_wrong(): + _check_fix_version(server, "KEY", "1.1") + unittest.TestCase().assertRaisesRegex(ValueError, + "No existe la fix version '1.1'", + call_check_fix_version_wrong)