diff --git a/README.rst b/README.rst index bc34d33..03c7b33 100644 --- a/README.rst +++ b/README.rst @@ -153,6 +153,7 @@ Requires - requests >= 2.13.0 - six - retry>=0.9.2 +- marshmallow>=3.2.0 Installation ============ @@ -193,6 +194,7 @@ Usage # resolved_statuses = comma separated list of statuses (closed, resolved) # run_test_case = True (default value for 'run' parameter) # connection_error_strategy [strict|skip|ignore] Choose how to handle connection errors + # return_jira_metadata = False (return Jira issue with metadata instead of boolean result) You can set the password field by setting the PYTEST_JIRA_PASSWORD environment variable: diff --git a/issue_model.py b/issue_model.py new file mode 100644 index 0000000..d204bec --- /dev/null +++ b/issue_model.py @@ -0,0 +1,80 @@ +from marshmallow import Schema, fields, EXCLUDE + + +class Basic(Schema): + id = fields.String() + name = fields.String() + + +class Components(Schema): + name = fields.String() + + +class Version(Schema): + name = fields.String() + + +class Priority(Basic): + pass + + +class Resolution(Basic): + description = fields.String() + + +class Status(Basic): + description = fields.String() + + +class Type(Basic): + subtask = fields.Boolean() + + +class User(Schema): + key = fields.String() + name = fields.String() + displayName = fields.String() + active = fields.Boolean() + + +class JiraIssueSchema(Schema): + class Meta: + unknown = EXCLUDE # exclude unknown fields + # Default set to None for fields that are not filled + issuetype = fields.Nested(Type(), default=None) + status = fields.Nested(Status(), default=None) + priority = fields.Nested(Priority(), default=None) + reporter = fields.Nested(User(), default=None) + creator = fields.Nested(User(), default=None) + versions = fields.List(fields.Nested(Version()), default=None) + summary = fields.String(default=None) + updated = fields.String(default=None) + created = fields.String(default=None) + resolutiondate = fields.String(default=None) + duedate = fields.String(default=None) + fixVersions = fields.List(fields.Nested(Version()), default=None) + components = fields.List(fields.Nested(Components()), default=None) + resolution = fields.Nested(Resolution(), default=None) + assignee = fields.Nested(User(), default=None) + labels = fields.List(fields.String()) + + +class JiraIssue: + def __init__(self, issue_id, **entries): + self.__dict__.update(entries) + self.issue_id = issue_id + + def __repr__(self): + return 'JiraIssue {}'.format(self.issue_id) + + @property + def components_list(self): + return set(component['name'] for component in self.components) + + @property + def fixed_versions(self): + return set(version['name'] for version in self.fix_versions) + + @property + def versions_list(self): + return set(version['name'] for version in self.versions) diff --git a/pytest_jira.py b/pytest_jira.py index c5a17cb..bcdb926 100644 --- a/pytest_jira.py +++ b/pytest_jira.py @@ -19,6 +19,8 @@ import six from retry import retry +from issue_model import JiraIssue, JiraIssueSchema + DEFAULT_RESOLVE_STATUSES = 'closed', 'resolved' DEFAULT_RUN_TEST_CASE = True CONNECTION_SKIP_MESSAGE = 'Jira connection issue, skipping test: %s' @@ -40,7 +42,8 @@ def __init__( resolved_statuses=None, run_test_case=DEFAULT_RUN_TEST_CASE, strict_xfail=False, - connection_error_strategy=None + connection_error_strategy=None, + return_jira_metadata=False ): self.conn = connection self.mark = marker @@ -56,6 +59,7 @@ def __init__( self.issue_cache = dict() self.strict_xfail = strict_xfail + self.return_jira_metadata = return_jira_metadata def is_issue_resolved(self, issue_id): """ @@ -65,12 +69,17 @@ def is_issue_resolved(self, issue_id): # Access Jira issue (may be cached) if issue_id not in self.issue_cache: try: - self.issue_cache[issue_id] = self.conn.get_issue(issue_id) + self.issue_cache[issue_id] = self.conn.get_issue( + issue_id, self.return_jira_metadata + ) except requests.RequestException as e: if not hasattr(e.response, 'status_code') \ or not e.response.status_code == 404: raise self.issue_cache[issue_id] = self.mark.get_default(issue_id) + if self.return_jira_metadata: + issue = JiraIssueSchema().dump(self.issue_cache[issue_id]) + return JiraIssue(issue_id, **issue) # Skip test if issue remains unresolved if self.issue_cache[issue_id] is None: @@ -217,7 +226,7 @@ def check_connection(self): return True @retry(JSONDecodeError, tries=3, delay=2) - def get_issue(self, issue_id): + def get_issue(self, issue_id, return_jira_metadata): if not self.is_connected: self.check_connection() issue_url = '{url}/rest/api/2/issue/{issue_id}'.format( @@ -225,18 +234,19 @@ def get_issue(self, issue_id): ) issue = self._jira_request(issue_url).json() field = issue['fields'] - return { - 'components': set( - c['name'] for c in field.get('components', set()) - ), - 'versions': set( - v['name'] for v in field.get('versions', set()) - ), - 'fixed_versions': set( - v['name'] for v in field.get('fixVersions', set()) - ), - 'status': field['status']['name'].lower(), - } + return field if return_jira_metadata else \ + { + 'components': set( + c['name'] for c in field.get('components', set()) + ), + 'versions': set( + v['name'] for v in field.get('versions', set()) + ), + 'fixed_versions': set( + v['name'] for v in field.get('fixVersions', set()) + ), + 'status': field['status']['name'].lower(), + } def get_url(self): return self.url @@ -444,6 +454,12 @@ def pytest_addoption(parser): skip - skip any test that has a marker """ ) + group.addoption('--jira-return-metadata', + action='store_true', + dest='return_jira_metadata', + default=False, + help='If set, will return Jira issue with ticket metadata' + ) def pytest_configure(config): @@ -495,7 +511,8 @@ def pytest_configure(config): resolved_statuses, config.getvalue('jira_run_test_case'), config.getini("xfail_strict"), - config.getvalue('jira_connection_error_strategy') + config.getvalue('jira_connection_error_strategy'), + config.getvalue('return_jira_metadata') ) ok = config.pluginmanager.register(jira_plugin, PLUGIN_NAME) assert ok @@ -514,7 +531,10 @@ def wrapper_jira_issue(issue_id): jira_plugin = request.config.pluginmanager.getplugin(PLUGIN_NAME) if jira_plugin: try: - return not jira_plugin.is_issue_resolved(issue_id) + result = jira_plugin.is_issue_resolved(issue_id) + if request.config.option.return_jira_metadata: + return result + return not result # return boolean representing of issue state except requests.RequestException as e: strategy = request.config.getoption(CONNECTION_ERROR_FLAG_NAME) if strategy == SKIP: diff --git a/requirements.txt b/requirements.txt index f2a700d..fd4f16d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ pytest>=2.2.4 six requests>=2.13.0 retry>=0.9.2 +marshmallow>=3.2.0 diff --git a/tests/test_jira.py b/tests/test_jira.py index c09f98d..65ebcef 100644 --- a/tests/test_jira.py +++ b/tests/test_jira.py @@ -1,7 +1,8 @@ import os -import pytest from distutils.version import LooseVersion +import pytest + CONFTEST = """ import pytest @@ -1017,3 +1018,26 @@ def test_pass(jira_issue): failed=failed, error=error ) + + +@pytest.mark.parametrize("ticket", ['ORG-1382', 'Foo-Bar']) +@pytest.mark.parametrize("return_method, _type", [ + ('--jira-return-metadata', 'JiraIssue'), + ('', 'bool'), +]) +def test_jira_fixture_return_metadata(testdir, return_method, _type, ticket): + testdir.makepyfile(""" + import pytest + from issue_model import JiraIssue + + def test_pass(jira_issue): + issue = jira_issue('%s') + assert isinstance(issue, %s) + """ % (ticket, _type)) + ARGS = ( + '--jira', + '--jira-url', 'https://issues.jboss.org', + return_method + ) + result = testdir.runpytest(*ARGS) + result.assert_outcomes(1, 0, 0)