-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2c4c50a
commit 2d70b6c
Showing
20 changed files
with
2,444 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import os | ||
import sys | ||
|
||
import requests | ||
|
||
from pytest_testrail.exceptions import MissingCloudCredentialError | ||
|
||
# pylint: disable=import-error | ||
if sys.version_info[0] == 2: | ||
import ConfigParser as configparser | ||
else: | ||
import configparser | ||
|
||
|
||
class Session: | ||
__default_headers = { | ||
'Content-Type': 'application/json', | ||
'User-Agent': 'TestRail API v: 2' | ||
} | ||
|
||
def __init__(self, base_url: str = None, email: str = None, key: str = None, **kwargs): | ||
email = email or self.__get_credential('email', ['TESTRAIL_EMAIL']) | ||
key = key or self.__get_credential('key', ['TESTRAIL_KEY']) | ||
base_url = base_url or self.__get_credential('url', ['TESTRAIL_URL']) | ||
verify_ssl = bool(self.__get_credential('verify_ssl', ['TESTRAIL_VERIFY_URL'])) | ||
|
||
self.__base_url = f'{base_url}/index.php?/api/v2/' | ||
self.__user = email | ||
self.__password = key | ||
self.__headers = kwargs.get('headers', self.__default_headers) | ||
self.__verify_ssl = kwargs.get('verify_ssl', verify_ssl) | ||
self.__timeout = kwargs.get('timeout', 5) | ||
self.__session = requests.Session() | ||
|
||
@property | ||
def __name(self): | ||
return type(self).__name__ | ||
|
||
@property | ||
def __config(self): | ||
name = '.{0}'.format(self.__name.lower()) | ||
config = configparser.ConfigParser() | ||
config.read([name, os.path.join(os.path.expanduser('~'), name)]) | ||
return config | ||
|
||
def __get_credential(self, key, envs): | ||
try: | ||
return self.__config.get('credentials', key) | ||
except (configparser.NoSectionError, configparser.NoOptionError, KeyError): | ||
for env in envs: | ||
value = os.getenv(env) | ||
if value: | ||
return value | ||
raise MissingCloudCredentialError(self.__name, key, envs) | ||
|
||
def request(self, method: str, src: str, **kwargs): | ||
""" | ||
Base request method | ||
:param method: | ||
:param src: | ||
:param kwargs: | ||
:return: response | ||
""" | ||
url = f'{self.__base_url}{src}' | ||
response = self.__session.request(method, url, auth=(self.__user, self.__password), headers=self.__headers, | ||
**kwargs) | ||
return response.json() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import pytest | ||
|
||
|
||
class MissingCloudCredentialError(pytest.UsageError): | ||
def __init__(self, driver, key, envs): | ||
super(MissingCloudCredentialError, self).__init__( | ||
"{0} {1} must be set. Try setting one of the following " | ||
"environment variables {2}, or see the documentation for " | ||
"how to use a configuration file.".format(driver, key, envs) | ||
) | ||
|
||
|
||
class InvalidOptionError(Exception): | ||
def __init__(self, option): | ||
Exception.__init__(self, 'Option %s is not a valid choice' % option) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import re | ||
from datetime import timedelta | ||
|
||
|
||
class TestRailError(Exception): | ||
pass | ||
|
||
|
||
class ContainerIter: | ||
def __init__(self, objs): | ||
self._objs = list(objs) | ||
|
||
def __len__(self): | ||
return len(self._objs) | ||
|
||
def __getitem__(self, index): | ||
return self._objs[index] | ||
|
||
|
||
CUSTOM_METHODS_RE = re.compile(r'^custom_(\w+)') | ||
|
||
|
||
def custom_methods(content): | ||
matches = [CUSTOM_METHODS_RE.match(method) for method in content] | ||
return dict({match.string: content[match.string] for match in matches if match}) | ||
|
||
|
||
def testrail_duration_to_timedelta(duration): | ||
span = __span() | ||
timedelta_map = { | ||
'weeks': span(re.search(r'\d+w', duration)), | ||
'days': span(re.search(r'\d+d', duration)), | ||
'hours': span(re.search(r'\d+h', duration)), | ||
'minutes': span(re.search(r'\d+m', duration)), | ||
'seconds': span(re.search(r'\d+s', duration)) | ||
} | ||
return timedelta(**timedelta_map) | ||
|
||
|
||
def __span(): | ||
return lambda x: int(x.group(0)[:-1]) if x else 0 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
from datetime import datetime | ||
|
||
from pytest_testrail.helper import TestRailError, custom_methods | ||
|
||
|
||
class Case(object): | ||
def __init__(self, content=None): | ||
self._content = content or dict() | ||
self._custom_methods = custom_methods(self._content) | ||
|
||
def __getattr__(self, attr): | ||
if attr in self._custom_methods: | ||
return self._content.get(self._custom_methods[attr]) | ||
raise AttributeError('"{}" object has no attribute "{}"'.format( | ||
self.__class__.__name__, attr)) | ||
|
||
def __str__(self): | ||
return self.title | ||
|
||
@property | ||
def created_by(self): | ||
return self._content.get('created_by') | ||
|
||
@property | ||
def created_on(self): | ||
return datetime.fromtimestamp(int(self._content.get('created_on'))) | ||
|
||
@property | ||
def estimate(self): | ||
return self._content.get('estimate') | ||
|
||
@estimate.setter | ||
def estimate(self, value): | ||
# TODO should have some logic to validate format of timespan | ||
if not isinstance(value, str): | ||
raise TestRailError('input must be a string') | ||
self._content['estimate'] = value | ||
|
||
@property | ||
def estimated_forecast(self): | ||
return self._content.get('estimated_forecast') | ||
|
||
@property | ||
def id(self): | ||
return self._content.get('id') | ||
|
||
@property | ||
def milestone_id(self): | ||
return self._content.get('milestone_id') | ||
|
||
@milestone_id.setter | ||
def milestone_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an int') | ||
self._content['milestone_id'] = value | ||
|
||
@property | ||
def priority_id(self): | ||
return self._content.get('priority_id') | ||
|
||
@priority_id.setter | ||
def priority_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an int') | ||
self._content['priority_id'] = value | ||
|
||
@property | ||
def refs(self): | ||
refs = self._content.get('refs') | ||
return refs.split(',') if refs else list() | ||
|
||
@refs.setter | ||
def refs(self, value): | ||
if not isinstance(value, list): | ||
raise TestRailError('input must be a list') | ||
self._content['refs'] = ','.join(value) | ||
|
||
@property | ||
def section_id(self): | ||
return self._content.get('section_id') | ||
|
||
@section_id.setter | ||
def section_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an int') | ||
self._content['section_id'] = value | ||
|
||
@property | ||
def suite_id(self): | ||
return self._content.get('suite_id') | ||
|
||
@suite_id.setter | ||
def suite_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an int') | ||
self._content['suite_id'] = value | ||
|
||
@property | ||
def template_id(self): | ||
return self._content.get('template_id') | ||
|
||
@template_id.setter | ||
def template_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an id') | ||
self._content['template_id'] = value | ||
|
||
@property | ||
def title(self): | ||
return self._content.get('title') | ||
|
||
@title.setter | ||
def title(self, value): | ||
if not isinstance(value, (str, str)): | ||
raise TestRailError('input must be a string') | ||
self._content['title'] = value | ||
|
||
@property | ||
def type_id(self): | ||
return self._content.get('type_id') | ||
|
||
@type_id.setter | ||
def type_id(self, value): | ||
if not isinstance(value, int): | ||
raise TestRailError('input must be an id') | ||
self._content['type_id'] = value | ||
|
||
@property | ||
def updated_by(self): | ||
return self._content.get('updated_by') | ||
|
||
@property | ||
def updated_on(self): | ||
return datetime.fromtimestamp(int(self._content.get('updated_on'))) | ||
|
||
def raw_data(self): | ||
return self._content |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
class CaseType(object): | ||
def __init__(self, content): | ||
self._content = content | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
@property | ||
def id(self): | ||
return self._content.get('id') | ||
|
||
@property | ||
def is_default(self): | ||
return self._content.get('is_default') | ||
|
||
@property | ||
def name(self): | ||
return self._content.get('name') |
Oops, something went wrong.