예제 #1
1
파일: test_api.py 프로젝트: ikorin/testrail
 def setUp(self):
     self.client = API()
     self.mock_user_data = [
         {
             "email": "*****@*****.**",
             "id": 1,
             "is_active": 'true',
             "name": "Han Solo"
         },
         {
             "email": "*****@*****.**",
             "id": 2,
             "is_active": 'true',
             "name": "Jabba the Hutt"
         }
     ]
예제 #2
0
class TestBase(unittest.TestCase):
    def setUp(self):
        self.client = API()

    def test_set_project_id(self):
        self.client.set_project_id(20)
        self.assertEqual(self.client._project_id, 20)
예제 #3
0
 def test_no_env(self):
     client = API()
     config = client._conf()
     self.assertEqual(config['email'], '*****@*****.**')
     self.assertEqual(config['key'], 'your_api_key')
     self.assertEqual(config['url'], 'https://<server>')
     self.assertEqual(client.verify_ssl, True)
예제 #4
0
파일: test_api.py 프로젝트: ikorin/testrail
class TestHTTPMethod(unittest.TestCase):
    def setUp(self):
        self.client = API()

    @mock.patch('testrail.api.requests.get')
    def test_get_ok(self, mock_get):
        mock_response = mock.Mock()
        return_value = {
            "announcement": "..",
            "completed_on": None,
            "id": 1,
            "is_completed": False,
            "name": "Datahub",
            "show_announcement": True,
            "url": "http://<server>/index.php?/projects/overview/1"
        }

        expected_response = copy.deepcopy(return_value)
        mock_response.json.return_value = return_value
        mock_response.status_code = 200
        mock_get.return_value = mock_response

        url = 'https://<server>/index.php?/api/v2/get_project/1'
        actual_response = self.client._get('get_project/1')
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            auth=('*****@*****.**', 'your_api_key')
        )
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_bad_no_params(self, mock_get):
        mock_response = mock.Mock()
        expected_response = {
            'url': 'https://<server>/index.php?/api/v2/get_plan/200',
            'status_code': 400,
            'payload': None,
            'error': 'Invalid or unknown test plan'
        }
        url = 'https://<server>/index.php?/api/v2/get_plan/200'
        mock_response.json.return_value = {
            'error': 'Invalid or unknown test plan'
        }
        mock_response.status_code = 400
        mock_response.url = url
        mock_get.return_value = mock_response

        with self.assertRaises(TestRailError) as e:
            self.client._get('get_plan/200')
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            auth=('*****@*****.**', 'your_api_key')
        )
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, ast.literal_eval(str(e.exception)))
예제 #5
0
    def setUp(self):
        self.client = API()
        self.mock_project_data = [{
            "announcement":
            "..",
            "completed_on":
            None,
            "id":
            1,
            "is_completed":
            False,
            "name":
            "Project1",
            "show_announcement":
            True,
            "url":
            "http://<server>/index.php?/projects/overview/1"
        }, {
            "announcement":
            "..",
            "completed_on":
            False,
            "id":
            2,
            "is_completed":
            True,
            "name":
            "Project2",
            "show_announcement":
            True,
            "url":
            "http://<server>/index.php?/projects/overview/2"
        }]

        self.projects = copy.deepcopy(self.mock_project_data)
예제 #6
0
 def test_key_env(self):
     key = 'itgiwiht84inf92GWT'
     os.environ['TESTRAIL_USER_KEY'] = key
     client = API()
     config = client._conf()
     self.assertEqual(config['email'], '*****@*****.**')
     self.assertEqual(config['key'], key)
     self.assertEqual(config['url'], 'https://<server>')
예제 #7
0
 def test_url_env(self):
     url = 'https://example.com'
     os.environ['TESTRAIL_URL'] = url
     client = API()
     config = client._conf()
     self.assertEqual(config['email'], '*****@*****.**')
     self.assertEqual(config['key'], 'your_api_key')
     self.assertEqual(config['url'], url)
예제 #8
0
 def test_user_env(self):
     email = '*****@*****.**'
     os.environ['TESTRAIL_USER_EMAIL'] = email
     client = API()
     config = client._conf()
     self.assertEqual(config['email'], email)
     self.assertEqual(config['key'], 'your_api_key')
     self.assertEqual(config['url'], 'https://<server>')
예제 #9
0
    def setUp(self):
        API.flush_cache()

        self.mock_result_data = {
            'assignedto_id': 1,
            'comment': 'All steps passed',
            'created_by': 2,
            'created_on': 1453504099,
            'defects': 'def1, def2, def3',
            'elapsed': '1w 3d 6h 2m 30s',
            'id': 3,
            'status_id': 1,
            'test_id': 5,
            'version': '1.0RC'
        }

        self.mock_user_data = [{
            "email": "*****@*****.**",
            "id": 1,
            "is_active": True,
            "name": "Han Solo"
        }, {
            "email": "*****@*****.**",
            "id": 2,
            "is_active": True,
            "name": "Jabba the Hutt"
        }]

        self.mock_status_data = [{
            "color_bright": 12709313,
            "color_dark": 6667107,
            "color_medium": 9820525,
            "id": 1,
            "is_final": True,
            "is_system": True,
            "is_untested": True,
            "label": "Passed",
            "name": "passed"
        }]

        self.mock_test_data = [{
            "assignedto_id": 1,
            "case_id": 1,
            "estimate": "1m 5s",
            "estimate_forecast": None,
            "id": 5,
            "priority_id": 2,
            "run_id": 1,
            "status_id": 5,
            "title": "Verify line spacing on multi-page document",
            "type_id": 4
        }]

        self.result = Result(self.mock_result_data)
예제 #10
0
 def test_plancontainer_created_by_error(self, mock_get):
     API.flush_cache()
     mock_response = mock.Mock()
     mock_response.json.side_effect = [
         self.mock_plans_data, self.mock_users
     ]
     mock_response.status_code = 200
     mock_get.return_value = mock_response
     user = 3
     with self.assertRaises(TestRailError) as e:
         created_by = self.client.plans().created_by(user)
     self.assertEqual(str(e.exception), 'Must pass in a User object')
예제 #11
0
    def setUp(self):
        self.client = API()
        self.client.set_project_id(1)
        self.mock_suites_data_1 = [{
            "description":
            "..",
            "id":
            1,
            "name":
            "Setup & Installation",
            "project_id":
            1,
            "url":
            "http://<server>/index.php?/suites/view/1"
        }, {
            "description":
            "..",
            "id":
            2,
            "name":
            "Setup & Installation",
            "project_id":
            1,
            "url":
            "http://<server>/index.php?/suites/view/2"
        }]
        self.mock_suites_data_2 = [{
            "description":
            "..",
            "id":
            3,
            "name":
            "Setup & Installation",
            "project_id":
            2,
            "url":
            "http://<server>/index.php?/suites/view/1"
        }, {
            "description":
            "..",
            "id":
            4,
            "name":
            "Setup & Installation",
            "project_id":
            2,
            "url":
            "http://<server>/index.php?/suites/view/2"
        }]

        self.suites_1 = copy.deepcopy(self.mock_suites_data_1)
        self.suites_2 = copy.deepcopy(self.mock_suites_data_2)
예제 #12
0
 def test_plancontainer_created_by(self, mock_get):
     API.flush_cache()
     mock_response = mock.Mock()
     mock_response.json.side_effect = [
         self.mock_plans_data, self.mock_users
     ]
     mock_response.status_code = 200
     mock_get.return_value = mock_response
     user = User({'id': 3})
     created_by = self.client.plans().created_by(user)
     self.assertTrue([lambda x: isinstance(x, Plan) for x in created_by])
     self.assertEqual(len(created_by), 2)
     self.assertEqual(created_by[0].id, 33)
     self.assertEqual(created_by[1].id, 44)
예제 #13
0
 def __init__(self,
              project_id=0,
              email=None,
              key=None,
              url=None,
              verify_ssl=True,
              proxies=None):
     self.api = API(email=email,
                    key=key,
                    url=url,
                    verify_ssl=verify_ssl,
                    proxies=proxies)
     self.api.set_project_id(project_id)
     self._project_id = project_id
예제 #14
0
 def test_no_config_file(self):
     os.remove(self.config_path)
     key = 'itgiwiht84inf92GWT'
     email = '*****@*****.**'
     url = 'https://example.com'
     os.environ['TESTRAIL_URL'] = url
     os.environ['TESTRAIL_USER_KEY'] = key
     os.environ['TESTRAIL_USER_EMAIL'] = email
     client = API()
     config = client._conf()
     self.assertEqual(config['url'], url)
     self.assertEqual(config['key'], key)
     self.assertEqual(config['email'], email)
     self.assertEqual(client.verify_ssl, True)
예제 #15
0
    def setUp(self):
        self.client = API()
        self.mock_user_data = [{
            "email": "*****@*****.**",
            "id": 1,
            "is_active": 'true',
            "name": "Han Solo"
        }, {
            "email": "*****@*****.**",
            "id": 2,
            "is_active": 'true',
            "name": "Jabba the Hutt"
        }]

        self.users = copy.deepcopy(self.mock_user_data)
예제 #16
0
class Run(TestRailBase):
    def __init__(self, content=None):
        self._content = content or dict()
        self.api = API()

    @property
    def assigned_to(self):
        return User(self.api.user_with_id(self._content.get('assignedto_id')))

    @property
    def blocked_count(self):
        return self._content.get('blocked_count')

    @property
    def cases(self):
        if self._content.get('case_ids'):
            cases = list(map(self.api.case_with_id, self._content.get('case_ids')))
            return list(map(Case, cases))
        else:
            return list()


    @cases.setter
    def cases(self, cases):
        exc_msg = 'cases must be set to None or a container of Case objects'

        if cases is None:
            self._content['case_ids'] = None

        elif not isinstance(cases, (list, tuple)):
            raise TestRailError(exc_msg)

        elif not all([isinstance(case, Case) for case in cases]):
예제 #17
0
 def test_config_no_key(self):
     os.remove(self.config_path)
     shutil.copyfile('%s/testrail.conf-nokey' % self.test_dir,
                     self.config_path)
     with self.assertRaises(TestRailError) as e:
         API()
     self.assertEqual(str(e.exception),
                      ('A password or API key must be set in environment ' +
                       'variable TESTRAIL_USER_KEY or in ~/.testrail.conf'))
예제 #18
0
 def test_config_no_url(self):
     os.remove(self.config_path)
     shutil.copyfile('%s/testrail.conf-nourl' % self.test_dir,
                     self.config_path)
     with self.assertRaises(TestRailError) as e:
         API()
     self.assertEqual(str(e.exception),
                      ('A URL must be set in environment ' +
                       'variable TESTRAIL_URL or in ~/.testrail.conf'))
예제 #19
0
 def test_get_users_cache_timeout(self, mock_get):
     self.client = API()
     mock_response = mock.Mock()
     expected_response = self.users
     url = 'https://<server>/index.php?/api/v2/get_users'
     mock_response.json.return_value = self.mock_user_data
     mock_response.status_code = 200
     mock_get.return_value = mock_response
     actual_response = self.client.users()
     timeout = self.client._timeout
     self.client._users['ts'] = datetime.now() - timedelta(seconds=timeout)
     actual_response = self.client.users()  # verity cache timed out
     c = mock.call(url,
                   headers={'Content-Type': 'application/json'},
                   params=None,
                   verify=True,
                   auth=('*****@*****.**', 'your_api_key'))
     mock_get.assert_has_calls([c, mock.call().json()] * 2)
     self.assertEqual(2, mock_response.json.call_count)
     self.assertEqual(expected_response, actual_response)
예제 #20
0
파일: test_api.py 프로젝트: ikorin/testrail
 def test_get_users_cache_timeout(self, mock_get):
     self.client = API()
     mock_response = mock.Mock()
     expected_response = copy.deepcopy(self.mock_user_data)
     url = 'https://<server>/index.php?/api/v2/get_users'
     mock_response.json.return_value = self.mock_user_data
     mock_response.status_code = 200
     mock_get.return_value = mock_response
     actual_response = self.client.users()
     timeout = self.client._timeout
     self.client._users['ts'] = datetime.now() - timedelta(seconds=timeout)
     actual_response = self.client.users()  # verity cache timed out
     mock_get.assert_called_twice_with(
         url,
         headers={'Content-Type': 'application/json'},
         params=None,
         auth=('*****@*****.**', 'your_api_key')
     )
     self.assertEqual(2, mock_response.json.call_count)
     self.assertEqual(expected_response, actual_response)
예제 #21
0
class Entry(TestRailBase):
    def __init__(self, content):
        self._content = content
        self._api = API()

    @property
    def id(self):
        return self._content.get('id')

    @property
    def name(self):
        return self._content.get('name')

    @property
    def runs(self):
        return list(map(EntryRun, self._content.get('runs')))

    @property
    def suite(self):
        return Suite(self._api.suite_with_id(self._content.get('suite_id')))
예제 #22
0
 def __init__(self, content):
     self._content = content
     self._api = API()
예제 #23
0
 def setUp(self):
     self.client = API()
예제 #24
0
파일: test_api.py 프로젝트: ikorin/testrail
class TestUser(unittest.TestCase):
    def setUp(self):
        self.client = API()
        self.mock_user_data = [
            {
                "email": "*****@*****.**",
                "id": 1,
                "is_active": 'true',
                "name": "Han Solo"
            },
            {
                "email": "*****@*****.**",
                "id": 2,
                "is_active": 'true',
                "name": "Jabba the Hutt"
            }
        ]

    def tearDown(self):
        util.reset_shared_state(self.client)

    @mock.patch('testrail.api.requests.get')
    def test_get_users(self, mock_get):
        mock_response = mock.Mock()
        expected_response = copy.deepcopy(self.mock_user_data)
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.users()
        timeout = self.client._timeout
        # set timeout to 1 second from now
        delta = timedelta(seconds=timeout-1)
        self.client._users['ts'] = datetime.now() - delta
        actual_response = self.client.users()  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            auth=('*****@*****.**', 'your_api_key')
        )
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_users_cache_timeout(self, mock_get):
        self.client = API()
        mock_response = mock.Mock()
        expected_response = copy.deepcopy(self.mock_user_data)
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.users()
        timeout = self.client._timeout
        self.client._users['ts'] = datetime.now() - timedelta(seconds=timeout)
        actual_response = self.client.users()  # verity cache timed out
        mock_get.assert_called_twice_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            auth=('*****@*****.**', 'your_api_key')
        )
        self.assertEqual(2, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)
예제 #25
0
class TestSuite(unittest.TestCase):
    def setUp(self):
        self.client = API()
        self.client.set_project_id(1)
        self.mock_suites_data_1 = [{
            "description":
            "..",
            "id":
            1,
            "name":
            "Setup & Installation",
            "project_id":
            1,
            "url":
            "http://<server>/index.php?/suites/view/1"
        }, {
            "description":
            "..",
            "id":
            2,
            "name":
            "Setup & Installation",
            "project_id":
            1,
            "url":
            "http://<server>/index.php?/suites/view/2"
        }]
        self.mock_suites_data_2 = [{
            "description":
            "..",
            "id":
            3,
            "name":
            "Setup & Installation",
            "project_id":
            2,
            "url":
            "http://<server>/index.php?/suites/view/1"
        }, {
            "description":
            "..",
            "id":
            4,
            "name":
            "Setup & Installation",
            "project_id":
            2,
            "url":
            "http://<server>/index.php?/suites/view/2"
        }]

        self.suites_1 = copy.deepcopy(self.mock_suites_data_1)
        self.suites_2 = copy.deepcopy(self.mock_suites_data_2)

    def tearDown(self):
        util.reset_shared_state(self.client)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.suites_1
        url = 'https://<server>/index.php?/api/v2/get_suites/1'
        mock_response.json.return_value = self.mock_suites_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.suites()
        actual_response = self.client.suites()  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites_with_project(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.suites_2
        url = 'https://<server>/index.php?/api/v2/get_suites/2'
        mock_response.json.return_value = self.mock_suites_data_2
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.suites(2)
        actual_response = self.client.suites(2)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites_invalid_project(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = {}
        mock_response.status_code = 400
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError):
            self.client.suites(20)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites_cache_timeout(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.suites_1
        url = 'https://<server>/index.php?/api/v2/get_suites/1'
        mock_response.json.return_value = self.mock_suites_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        self.client.suites()
        timeout = self.client._timeout
        self.client._suites[1]['ts'] = datetime.now() - timedelta(
            seconds=timeout)
        actual_response = self.client.suites()  # verify cache timeout
        c = mock.call(url,
                      headers={'Content-Type': 'application/json'},
                      params=None,
                      verify=True,
                      auth=('*****@*****.**', 'your_api_key'))
        mock_get.assert_has_calls([c, mock.call().json()] * 2)
        self.assertEqual(2, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites_different_projects_no_cache_hit(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.suites_1
        url = 'https://<server>/index.php?/api/v2/get_suites/1'
        url2 = 'https://<server>/index.php?/api/v2/get_suites/2'
        mock_response.json.return_value = self.mock_suites_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.suites()
        actual_response = self.client.suites(2)  # verify cache not hit
        c1 = mock.call(url,
                       headers={'Content-Type': 'application/json'},
                       params=None,
                       verify=True,
                       auth=('*****@*****.**', 'your_api_key'))
        c2 = mock.call(url2,
                       headers={'Content-Type': 'application/json'},
                       params=None,
                       verify=True,
                       auth=('*****@*****.**', 'your_api_key'))
        mock_get.assert_has_calls(
            [c1, mock.call().json(), c2,
             mock.call().json()])
        self.assertEqual(2, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_suite_with_id(self, mock_get):
        mock_response = mock.Mock()
        expected_response = next(
            filter(lambda x: x if x['id'] == 2 else None, self.suites_1))
        url = 'https://<server>/index.php?/api/v2/get_suites/1'
        mock_response.json.return_value = self.mock_suites_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.suite_with_id(2)
        actual_response = self.client.suite_with_id(2)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_suites_invalid_suite_id(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = {}
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError) as e:
            self.client.suite_with_id(20)
        self.assertEqual(str(e.exception), "Suite ID '20' was not found")
예제 #26
0
파일: test_api.py 프로젝트: ikorin/testrail
 def setUp(self):
     self.client = API()
예제 #27
0
class TestRail(object):
    def __init__(self, project_id=0, email=None, key=None, url=None):
        self.api = API(email=email, key=key, url=url)
        self.api.set_project_id(project_id)
        self._project_id = project_id

    def set_project_id(self, project_id):
        self._project_id = project_id
        self.api.set_project_id(project_id)

    # Post generics
    @methdispatch
    def add(self, obj):
        raise NotImplementedError

    @methdispatch
    def update(self, obj):
        raise NotImplementedError

    @methdispatch
    def close(self, obj):
        raise NotImplementedError

    @methdispatch
    def delete(self, obj):
        raise NotImplementedError

    # Project Methods
    def projects(self):
        return ProjectContainer(list(map(Project, self.api.projects())))

    @methdispatch
    def project(self):
        return Project()

    @project.register(str)
    @project.register(unicode)
    @singleresult
    def _project_by_name(self, name):
        return filter(lambda p: p.name == name, self.projects())

    @project.register(int)
    @singleresult
    def _project_by_id(self, project_id):
        return filter(lambda p: p.id == project_id, self.projects())

    # User Methods
    def users(self):
        return map(User, self.api.users())

    @methdispatch
    def user(self):
        return User()

    @user.register(int)
    @singleresult
    def _user_by_id(self, identifier):
        return filter(lambda u: u.id == identifier, self.users())

    @user.register(str)
    @user.register(unicode)
    @singleresult
    def _user_by_email_name(self, identifier):
        by_email = lambda u: u.email == identifier
        by_name = lambda u: u.name == identifier
        f = by_email if re.match('[^@]+@[^@]+\.[^@]+', identifier) else by_name
        return filter(f, self.users())

    def active_users(self):
        return list(filter(lambda u: u.is_active is True, self.users()))

    def inactive_users(self):
        return list(filter(lambda u: u.is_active is False, self.users()))

    # Suite Methods
    def suites(self):
        return map(Suite, self.api.suites(self._project_id))

    @methdispatch
    def suite(self):
        return Suite()

    @suite.register(str)
    @suite.register(unicode)
    @singleresult
    def _suite_by_name(self, name):
        return filter(lambda s: s.name.lower() == name.lower(), self.suites())

    @suite.register(int)
    @singleresult
    def _suite_by_id(self, suite_id):
        return filter(lambda s: s.id == suite_id, self.suites())

    def active_suites(self):
        return filter(lambda s: s.is_completed is False, self.suites())

    def completed_suites(self):
        return filter(lambda s: s.is_completed is True, self.suites())

    @add.register(Suite)
    def _add_suite(self, obj):
        obj.project = obj.project or self.project(self._project_id)
        return Suite(self.api.add_suite(obj.raw_data()))

    @update.register(Suite)
    def _update_suite(self, obj):
        return Suite(self.api.update_suite(obj.raw_data()))

    @delete.register(Suite)
    def _delete_suite(self, obj):
        return self.api.delete_suite(obj.id)

    # Milestone Methods
    def milestones(self):
        return map(Milestone, self.api.milestones(self._project_id))

    @methdispatch
    def milestone(self):
        return Milestone()

    @milestone.register(str)
    @milestone.register(unicode)
    @singleresult
    def _milestone_by_name(self, name):
        return filter(
            lambda m: m.name.lower() == name.lower(), self.milestones())

    @milestone.register(int)
    @singleresult
    def _milestone_by_id(self, milestone_id):
        return filter(lambda s: s.id == milestone_id, self.milestones())

    @add.register(Milestone)
    def _add_milestone(self, obj):
        obj.project = obj.project or self.project(self._project_id)
        return Milestone(self.api.add_milestone(obj.raw_data()))

    @update.register(Milestone)
    def _update_milestone(self, obj):
        return Milestone(self.api.update_milestone(obj.raw_data()))

    @delete.register(Milestone)
    def _delete_milestone(self, obj):
        return self.api.delete_milestone(obj.id)

    # Plan Methods
    @methdispatch
    def plans(self):
        return PlanContainer(list(map(Plan, self.api.plans(self._project_id))))

    @plans.register(Milestone)
    def _plans_for_milestone(self, obj):
        plans = filter(lambda p: p.milestone is not None, self.plans())
        return PlanContainer(filter(lambda p: p.milestone.id == obj.id, plans))

    @methdispatch
    def plan(self):
        return Plan()

    @plan.register(str)
    @plan.register(unicode)
    @singleresult
    def _plan_by_name(self, name):
        return filter(lambda p: p.name.lower() == name.lower(), self.plans())

    @plan.register(int)
    @singleresult
    def _plan_by_id(self, plan_id):
        return filter(lambda p: p.id == plan_id, self.plans())

    def completed_plans(self):
        return filter(lambda p: p.is_completed is True, self.plans())

    def active_plans(self):
        return filter(lambda p: p.is_completed is False, self.plans())

    @add.register(Plan)
    def _add_plan(self, obj, milestone=None):
        obj.project = obj.project or self.project(self._project_id)
        obj.milestone = milestone or obj.milestone
        return Plan(self.api.add_plan(obj.raw_data()))

    @update.register(Plan)
    def _update_plan(self, obj):
        return Plan(self.api.update_plan(obj.raw_data()))

    @close.register(Plan)
    def _close_plan(self, obj):
        return Plan(self.api.close_plan(obj.id))

    @delete.register(Plan)
    def _delete_plan(self, obj):
        return self.api.delete_plan(obj.id)

    # Run Methods
    @methdispatch
    def runs(self):
        return RunContainer(list(map(Run, self.api.runs(self._project_id))))

    @runs.register(Milestone)
    def _runs_for_milestone(self, obj):
        return RunContainer(filter(
            lambda r: r.milestone.id == obj.id, self.runs()))

    @runs.register(str)
    @runs.register(unicode)
    def _runs_by_name(self, name):
        # Returns all Runs that match :name, in descending order by ID
        runs = list(filter(lambda r: r.name.lower() == name.lower(), self.runs()))
        return sorted(runs, key=lambda r: r.id)

    @methdispatch
    def run(self):
        return Run()

    @run.register(str)
    @run.register(unicode)
    @singleresult
    def _run_by_name(self, name):
        # Returns the most recently created Run that matches :name
        runs = list(filter(lambda r: r.name.lower() == name.lower(), self.runs()))
        return sorted(runs, key=lambda r: r.id)[:1]

    @run.register(int)
    @singleresult
    def _run_by_id(self, run_id):
        filter(lambda p: p.id == run_id, self.runs())

    @add.register(Run)
    def _add_run(self, obj):
        obj.project = obj.project or self.project(self._project_id)
        return Run(self.api.add_run(obj.raw_data()))

    @update.register(Run)
    def _update_run(self, obj):
        return Run(self.api.update_run(obj.raw_data()))

    @close.register(Run)
    def _close_run(self, obj):
        return Run(self.api.close_run(obj.id))

    @delete.register(Run)
    def _delete_run(self, obj):
        return self.api.delete_run(obj.id)

    # Case Methods
    def cases(self, suite):
        return map(Case, self.api.cases(self._project_id, suite.id))

    @methdispatch
    def case(self):
        return Case()

    @case.register(str)
    @case.register(unicode)
    @singleresult
    def _case_by_title(self, title, suite):
        return filter(
            lambda c: c.title.lower() == title.lower(), self.cases(suite))

    @case.register(int)
    @singleresult
    def _case_by_id(self, case_id, suite=None):
        if suite is None:
            pass
        else:
            return filter(lambda c: c.id == case_id, self.cases(suite))

    @add.register(Case)
    def _add_case(self, obj):
        return Case(self.api.add_case(obj.raw_data()))

    # Test Methods
    def tests(self, run):
        return map(Test, self.api.tests(run.id))

    @methdispatch
    def test(self):
        return Test()

    @test.register(str)
    @test.register(unicode)
    @singleresult
    def _test_by_name(self, name):
        return filter(lambda t: t.name.lower() == name.lower(), self.tests())

    @test.register(int)
    @singleresult
    def _test_by_id(self, test_id, run):
        return filter(
            lambda t: t.raw_data()['case_id'] == test_id, self.tests(run))

    # Result Methods
    @methdispatch
    def results(self):
        raise TestRailError("Must request results by Run or Test")

    @results.register(Run)
    def _results_for_run(self, run):
        return ResultContainer(list(map(Result, self.api.results_by_run(run.id))))

    @results.register(Test)
    def _results_for_test(self, test):
        return ResultContainer(list(map(Result, self.api.results_by_test(test.id))))

    @methdispatch
    def result(self):
        return Result()

    @add.register(Result)
    def _add_result(self, obj):
        self.api.add_result(obj.raw_data())

    @add.register(tuple)
    def _add_results(self, results):
        obj, value = results
        if isinstance(obj, Run):
            self.api.add_results(map(lambda x: x.raw_data(), value), obj.id)

    # Section Methods
    def sections(self, suite=None):
        return map(Section, self.api.sections(suite_id=suite.id))

    @methdispatch
    def section(self):
        return Section()

    @section.register(int)
    def _section_by_id(self, section_id):
        return Section(self.api.section_with_id(section_id))

    @section.register(unicode)
    @section.register(str)
    @singleresult
    def _section_by_name(self, name, suite=None):
        return filter(lambda s: s.name == name, self.sections(suite))

    @add.register(Section)
    def _add_section(self, section):
        return Section(self.api.add_section(section.raw_data()))

    # Status Methods
    def statuses(self):
        return map(Status, self.api.statuses())

    @methdispatch
    def status(self):
        return Status()

    @status.register(str)
    @status.register(unicode)
    @singleresult
    def _status_by_name(self, name):
        return filter(lambda s: s.name == name.lower(), self.statuses())

    @status.register(int)
    @singleresult
    def _status_by_id(self, status_id):
        return filter(lambda s: s.id == status_id, self.statuses())

    def configs(self):
        return ConfigContainer(map(Config, self.api.configs()))
예제 #28
0
    def setUp(self):
        self.client = API()
        self.client.set_project_id(1)
        self.mock_plans_data_1 = [{
            "id": 1,
            "name": "Test Plan #1",
            "is_completed": False,
            "description": "..",
            "project_id": 1,
            "milestone_id": 1,
            "url": "http://<server>/index.php?/plans/view/1",
            "assignedto_id": None,
            "blocked_count": 1,
            "completed_on": None,
            "created_by": 1,
            "created_on": 1393845644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }, {
            "id": 2,
            "name": "Test Plan #2",
            "is_completed": False,
            "description": "..",
            "project_id": 1,
            "milestone_id": 2,
            "url": "http://<server>/index.php?/plans/view/2",
            "assignedto_id": None,
            "blocked_count": 1,
            "completed_on": None,
            "created_by": 1,
            "created_on": 1393845644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }]
        self.mock_plans_data_2 = [{
            "id": 3,
            "name": "Test Plan #3",
            "is_completed": False,
            "description": "..",
            "project_id": 2,
            "milestone_id": 3,
            "url": "http://<server>/index.php?/plans/view/3",
            "assignedto_id": 1,
            "blocked_count": 2,
            "completed_on": None,
            "created_by": 2,
            "created_on": 1393843644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }, {
            "id": 4,
            "name": "Test Plan #4",
            "is_completed": False,
            "description": "..",
            "project_id": 2,
            "milestone_id": 3,
            "url": "http://<server>/index.php?/plans/view/4",
            "assignedto_id": 1,
            "blocked_count": 2,
            "completed_on": None,
            "created_by": 2,
            "created_on": 1393843644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }]

        self.plans_1 = copy.deepcopy(self.mock_plans_data_1)
        self.plans_2 = copy.deepcopy(self.mock_plans_data_2)
예제 #29
0
class TestProject(unittest.TestCase):
    def setUp(self):
        self.client = API()
        self.mock_project_data = [{
            "announcement":
            "..",
            "completed_on":
            None,
            "id":
            1,
            "is_completed":
            False,
            "name":
            "Project1",
            "show_announcement":
            True,
            "url":
            "http://<server>/index.php?/projects/overview/1"
        }, {
            "announcement":
            "..",
            "completed_on":
            False,
            "id":
            2,
            "is_completed":
            True,
            "name":
            "Project2",
            "show_announcement":
            True,
            "url":
            "http://<server>/index.php?/projects/overview/2"
        }]

        self.projects = copy.deepcopy(self.mock_project_data)

    def tearDown(self):
        util.reset_shared_state(self.client)

    @mock.patch('testrail.api.requests.get')
    def test_get_projects(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.projects
        url = 'https://<server>/index.php?/api/v2/get_projects'
        mock_response.json.return_value = self.mock_project_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.projects()
        timeout = self.client._timeout
        # set timeout to 1 second from now
        delta = timedelta(seconds=timeout - 1)
        self.client._users['ts'] = datetime.now() - delta
        actual_response = self.client.projects()  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_projects_cache_timeout(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.projects
        url = 'https://<server>/index.php?/api/v2/get_projects'
        mock_response.json.return_value = self.mock_project_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.projects()
        timeout = self.client._timeout
        self.client._projects['ts'] = datetime.now() - timedelta(
            seconds=timeout)
        actual_response = self.client.projects()  # verify cache hit
        c = mock.call(url,
                      headers={'Content-Type': 'application/json'},
                      params=None,
                      verify=True,
                      auth=('*****@*****.**', 'your_api_key'))
        mock_get.assert_has_calls([c, mock.call().json()] * 2)
        self.assertEqual(2, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_project_id(self, mock_get):
        mock_response = mock.Mock()
        expected_response = next(
            filter(lambda x: x if x['id'] == 1 else None, self.projects))
        url = 'https://<server>/index.php?/api/v2/get_projects'
        mock_response.json.return_value = self.mock_project_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.project_with_id(1)
        actual_response = self.client.project_with_id(1)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_project_invalid_id(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = self.mock_project_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError) as e:
            self.client.project_with_id(300)
        err_msg = "Project ID '300' was not found"
        self.assertEqual(err_msg, str(e.exception))
예제 #30
0
class Case(TestRailBase):
    def __init__(self, content=None):
        self._content = content or dict()
        self.api = API()
        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))

    @property
    def created_by(self):
        user_id = self._content.get('created_by')
        return User(self.api.user_by_id(user_id))

    @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 timespa
        if not isinstance(value, (str, unicode)):
            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(self):
        m = self.api.milestone_with_id(self._content.get('milestone_id'))
        return Milestone(m) if m else Milestone()

    @milestone.setter
    def milestone(self, value):
        if not isinstance(value, Milestone):
            raise TestRailError('input must be a Milestone')
        self._content['milestone_id'] = value.id

    @property
    def priority(self):
        p = self.api.priority_with_id(self._content.get('priority_id'))
        return Priority(p) if p else Priority()

    @priority.setter
    def priority(self, value):
        if not isinstance(value, Priority):
            raise TestRailError('input must be a Priority')
        self._content['priority_id'] = value.id

    @property
    def refs(self):
        return self._content.get('refs').split(',')

    @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(self):
        s = self.api.section_with_id(self._content.get('section_id'))
        return Section(s) if s else Section()

    @section.setter
    def section(self, value):
        if not isinstance(value, Section):
            raise TestRailError('input must be a Section')
        self._content['section_id'] = value.id

    @property
    def suite(self):
        s = self.api.suite_with_id(self._content.get('suite_id'))
        return Suite(s) if s else Suite()

    @property
    def title(self):
        return self._content.get('title')

    @title.setter
    def title(self, value):
        if not isinstance(value, (str, unicode)):
            raise TestRailError('input must be a string')
        self._content['title'] = value

    @property
    def type(self):
        t = self.api.case_type_with_id(self._content.get('type_id'))
        return CaseType(t) if t else CaseType()

    @type.setter
    def type(self, value):
        if not isinstance(value, CaseType):
            raise TestRailError('input must be a CaseType')
        self._content['type_id'] = value.id

    @property
    def updated_by(self):
        user_id = self._content.get('updated_by')
        return User(self.api.user_by_id(user_id))

    @property
    def updated_on(self):
        return datetime.fromtimestamp(int(self._content.get('updated_on')))

    @property
    def template(self):
        # we don't get the template, on needed as a setter
        raise NotImplementedError

    @template.setter
    def template(self, value):
        if not isinstance(value, Template):
            raise TestRailError('input must be a Template')
        self._content['template_id'] = value.id

    def raw_data(self):
        return self._content
예제 #31
0
class TestUser(unittest.TestCase):
    def setUp(self):
        self.client = API()
        self.mock_user_data = [{
            "email": "*****@*****.**",
            "id": 1,
            "is_active": 'true',
            "name": "Han Solo"
        }, {
            "email": "*****@*****.**",
            "id": 2,
            "is_active": 'true',
            "name": "Jabba the Hutt"
        }]

        self.users = copy.deepcopy(self.mock_user_data)

    def tearDown(self):
        util.reset_shared_state(self.client)

    @mock.patch('testrail.api.requests.get')
    def test_get_users(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.users
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.users()
        timeout = self.client._timeout
        # set timeout to 1 second from now
        delta = timedelta(seconds=timeout - 1)
        self.client._users['ts'] = datetime.now() - delta
        actual_response = self.client.users()  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_users_cache_timeout(self, mock_get):
        self.client = API()
        mock_response = mock.Mock()
        expected_response = self.users
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.users()
        timeout = self.client._timeout
        self.client._users['ts'] = datetime.now() - timedelta(seconds=timeout)
        actual_response = self.client.users()  # verity cache timed out
        c = mock.call(url,
                      headers={'Content-Type': 'application/json'},
                      params=None,
                      verify=True,
                      auth=('*****@*****.**', 'your_api_key'))
        mock_get.assert_has_calls([c, mock.call().json()] * 2)
        self.assertEqual(2, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_user_with_id(self, mock_get):
        mock_response = mock.Mock()
        expected_response = next(
            filter(lambda x: x if x['id'] == 2 else None, self.users))
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.user_with_id(2)
        actual_response = self.client.user_with_id(2)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_user_invalid_id(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError) as e:
            self.client.user_with_id(300)
        err_msg = "User ID '300' was not found"
        self.assertEqual(err_msg, str(e.exception))

    @mock.patch('testrail.api.requests.get')
    def test_get_user_with_email(self, mock_get):
        mock_response = mock.Mock()
        expected_response = next(
            filter(lambda x: x
                   if x['email'] == '*****@*****.**' else None, self.users))
        url = 'https://<server>/index.php?/api/v2/get_users'
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.user_with_email('*****@*****.**')
        # verify cache hit
        actual_response = self.client.user_with_email('*****@*****.**')
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_user_invalid_email(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = self.mock_user_data
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError) as e:
            self.client.user_with_email('*****@*****.**')
        err_msg = "User email '*****@*****.**' was not found"
        self.assertEqual(err_msg, str(e.exception))
예제 #32
0
 def __init__(self, content=None):
     self._content = content or dict()
     self.api = API()
     self._custom_methods = custom_methods(self._content)
예제 #33
0
 def test_ssl_env(self):
     os.environ['TESTRAIL_VERIFY_SSL'] = 'False'
     client = API()
     self.assertEqual(client.verify_ssl, False)
예제 #34
0
 def __init__(self, project_id=0, email=None, key=None, url=None):
     self.api = API(email=email, key=key, url=url)
     self.api.set_project_id(project_id)
     self._project_id = project_id
예제 #35
0
class TestPlan(unittest.TestCase):
    def setUp(self):
        self.client = API()
        self.client.set_project_id(1)
        self.mock_plans_data_1 = [{
            "id": 1,
            "name": "Test Plan #1",
            "is_completed": False,
            "description": "..",
            "project_id": 1,
            "milestone_id": 1,
            "url": "http://<server>/index.php?/plans/view/1",
            "assignedto_id": None,
            "blocked_count": 1,
            "completed_on": None,
            "created_by": 1,
            "created_on": 1393845644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }, {
            "id": 2,
            "name": "Test Plan #2",
            "is_completed": False,
            "description": "..",
            "project_id": 1,
            "milestone_id": 2,
            "url": "http://<server>/index.php?/plans/view/2",
            "assignedto_id": None,
            "blocked_count": 1,
            "completed_on": None,
            "created_by": 1,
            "created_on": 1393845644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }]
        self.mock_plans_data_2 = [{
            "id": 3,
            "name": "Test Plan #3",
            "is_completed": False,
            "description": "..",
            "project_id": 2,
            "milestone_id": 3,
            "url": "http://<server>/index.php?/plans/view/3",
            "assignedto_id": 1,
            "blocked_count": 2,
            "completed_on": None,
            "created_by": 2,
            "created_on": 1393843644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }, {
            "id": 4,
            "name": "Test Plan #4",
            "is_completed": False,
            "description": "..",
            "project_id": 2,
            "milestone_id": 3,
            "url": "http://<server>/index.php?/plans/view/4",
            "assignedto_id": 1,
            "blocked_count": 2,
            "completed_on": None,
            "created_by": 2,
            "created_on": 1393843644,
            "untested_count": 6,
            "passed_count": 5,
            "failed_count": 2,
            "entries": []
        }]

        self.plans_1 = copy.deepcopy(self.mock_plans_data_1)
        self.plans_2 = copy.deepcopy(self.mock_plans_data_2)

    def tearDown(self):
        util.reset_shared_state(self.client)

    @mock.patch('testrail.api.requests.get')
    def test_get_plans(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.plans_1
        url = "https://<server>/index.php?/api/v2/get_plans/1"
        mock_response.json.return_value = self.mock_plans_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.plans()
        actual_response = self.client.plans()  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_plans_with_project(self, mock_get):
        mock_response = mock.Mock()
        expected_response = self.plans_2
        url = "https://<server>/index.php?/api/v2/get_plans/2"
        mock_response.json.return_value = self.mock_plans_data_2
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.plans(2)
        actual_response = self.client.plans(2)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_plans_invalid_project(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = {}
        mock_response.status_code = 400
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError):
            self.client.plans(20)

    @mock.patch('testrail.api.requests.get')
    def test_get_plan_with_id(self, mock_get):
        mock_response = mock.Mock()
        expected_response = next(
            filter(lambda x: x if x['id'] == 2 else None, self.plans_1))
        url = 'https://<server>/index.php?/api/v2/get_plans/1'
        mock_response.json.return_value = self.mock_plans_data_1
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        actual_response = self.client.plan_with_id(2)
        actual_response = self.client.plan_with_id(2)  # verify cache hit
        mock_get.assert_called_once_with(
            url,
            headers={'Content-Type': 'application/json'},
            params=None,
            verify=True,
            auth=('*****@*****.**', 'your_api_key'))
        self.assertEqual(1, mock_response.json.call_count)
        self.assertEqual(expected_response, actual_response)

    @mock.patch('testrail.api.requests.get')
    def test_get_plans_invalid_suite_id(self, mock_get):
        mock_response = mock.Mock()
        mock_response.json.return_value = {}
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        with self.assertRaises(TestRailError) as e:
            self.client.plan_with_id(30)
        self.assertEqual(str(e.exception), "Plan ID '30' was not found")