Skip to content

Commit c9b163c

Browse files
Merge pull request #338 from cgeisel/get-suites-pagination
update check_suite_id to handle paginated results
2 parents 966ca56 + e74e63f commit c9b163c

File tree

3 files changed

+121
-56
lines changed

3 files changed

+121
-56
lines changed

tests/test_api_request_handler.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def test_check_suite_exists_with_pagination(self, api_request_handler: ApiReques
163163
"offset": 0,
164164
"limit": 250,
165165
"size": 2,
166+
"_links": {"next": None, "prev": None},
166167
"suites": [
167168
{"id": 4, "name": "Suite1", "description": "Test1", "project_id": 3},
168169
{"id": 5, "name": "Suite2", "description": "Test2", "project_id": 3},
@@ -549,18 +550,82 @@ def test_check_missing_test_cases_ids_false(
549550
assert error == "", "No error should have occurred"
550551

551552
@pytest.mark.api_handler
552-
def test_get_suites_id(self, api_request_handler: ApiRequestHandler, requests_mock):
553+
def test_get_suite_ids(self, api_request_handler: ApiRequestHandler, requests_mock):
553554
project_id = 3
554555
mocked_response = [
555556
{"id": 100, "name": "Master"},
557+
{"id": 101, "name": "Smoke"},
556558
]
557559

558560
requests_mock.get(create_url(f"get_suites/{project_id}"), json=mocked_response)
559561
resources_added, error = api_request_handler.get_suite_ids(project_id)
560562
assert (
561-
resources_added[0] == mocked_response[0]["id"]
563+
resources_added[0] == mocked_response[0]["id"] and
564+
resources_added[1] == mocked_response[1]["id"]
562565
), "ID in response doesn't match mocked response"
563-
assert error == "", "Error occurred in get_suite_ids"
566+
567+
@pytest.mark.api_handler
568+
def test_get_suite_ids_error(
569+
self, api_request_handler: ApiRequestHandler, requests_mock
570+
):
571+
project_id = 3
572+
573+
requests_mock.get(
574+
create_url(f"get_suites/{project_id}"), exc=requests.exceptions.ConnectTimeout
575+
)
576+
577+
suite_ids, error = api_request_handler.get_suite_ids(project_id)
578+
579+
assert suite_ids == [], "Should return empty list on API error"
580+
assert error == "Your upload to TestRail did not receive a successful response from your TestRail Instance." \
581+
" Please check your settings and try again.", "Should return connection error message"
582+
583+
@pytest.mark.api_handler
584+
def test_resolve_suite_id_using_name(
585+
self, api_request_handler: ApiRequestHandler, requests_mock, mocker
586+
):
587+
project_id = 3
588+
suite_name = "Suite2"
589+
api_request_handler.suites_data_from_provider.name = suite_name
590+
591+
update_data_mock = mocker.patch('trcli.api.api_request_handler.ApiDataProvider.update_data')
592+
593+
mocked_response = {
594+
"offset": 0,
595+
"limit": 250,
596+
"size": 2,
597+
"_links": {"next": None, "prev": None},
598+
"suites": [
599+
{"id": 4, "name": "Suite1", "description": "Test1", "project_id": 3},
600+
{"id": 5, "name": "Suite2", "description": "Test2", "project_id": 3},
601+
]
602+
}
603+
604+
requests_mock.get(create_url(f"get_suites/{project_id}"), json=mocked_response)
605+
606+
suite_id, error = api_request_handler.resolve_suite_id_using_name(project_id)
607+
608+
assert suite_id == 5, "Should return the correct suite ID for matching name with pagination"
609+
assert error == "", "Should have no error message"
610+
611+
update_data_mock.assert_called_once_with([{"suite_id": 5, "name": "Suite2"}])
612+
613+
@pytest.mark.api_handler
614+
def test_resolve_suite_id_using_name_error(
615+
self, api_request_handler: ApiRequestHandler, requests_mock
616+
):
617+
project_id = 3
618+
619+
requests_mock.get(
620+
create_url(f"get_suites/{project_id}"), exc=requests.exceptions.ConnectTimeout
621+
)
622+
623+
suite_id, error = api_request_handler.resolve_suite_id_using_name(project_id)
624+
625+
assert suite_id == -1, "Should return -1 on API error"
626+
assert error == "Your upload to TestRail did not receive a successful response from your TestRail Instance." \
627+
" Please check your settings and try again.", "Should return connection error message"
628+
564629

565630
@pytest.mark.api_handler
566631
def test_return_project_error(

trcli/api/api_request_handler.py

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -122,83 +122,76 @@ def get_project_data(self, project_name: str, project_id: int = None) -> Project
122122
error_message=error,
123123
)
124124

125-
def check_suite_id(self, project_id: int) -> (bool, str):
125+
def check_suite_id(self, project_id: int) -> Tuple[bool, str]:
126126
"""
127127
Check if suite from DataProvider exist using get_suites endpoint
128128
:project_id: project id
129129
:returns: True if exists in suites. False if not.
130130
"""
131131
suite_id = self.suites_data_from_provider.suite_id
132-
response = self.client.send_get(f"get_suites/{project_id}")
133-
if not response.error_message:
134-
try:
135-
parsed = json.loads(response.response_text) if isinstance(response.response_text, str) else response.response_text
136-
suite_list = parsed.get("suites") if isinstance(parsed, dict) else parsed
137-
available_suites = [suite["id"] for suite in suite_list]
138-
return (
139-
(True, "")
140-
if suite_id in available_suites
141-
else (False, FAULT_MAPPING["missing_suite"].format(suite_id=suite_id))
142-
)
143-
except Exception as e:
144-
return None, f"Error parsing suites response: {e}"
132+
suites_data, error = self.__get_all_suites(project_id)
133+
if not error:
134+
available_suites = [
135+
suite
136+
for suite in suites_data
137+
if suite["id"] == suite_id
138+
]
139+
return (
140+
(True, "")
141+
if len(available_suites) > 0
142+
else (False, FAULT_MAPPING["missing_suite"].format(suite_id=suite_id))
143+
)
145144
else:
146-
return None, response.error_message
145+
return None, suites_data.error_message
147146

148147
def resolve_suite_id_using_name(self, project_id: int) -> Tuple[int, str]:
149148
"""Get suite ID matching suite name on data provider or returns -1 if unable to match any suite.
150149
:arg project_id: project id
151150
:returns: tuple with id of the suite and error message"""
152151
suite_id = -1
153-
error_message = ""
154-
response = self.client.send_get(f"get_suites/{project_id}")
155-
if not response.error_message:
156-
try:
157-
parsed = json.loads(response.response_text) if isinstance(response.response_text, str) else response.response_text
158-
suite_list = parsed.get("suites") if isinstance(parsed, dict) else parsed
159-
suite = next(
160-
filter(lambda x: x["name"] == self.suites_data_from_provider.name, suite_list),
161-
None
162-
)
163-
if suite:
152+
suite_name = self.suites_data_from_provider.name
153+
suites_data, error = self.__get_all_suites(project_id)
154+
if not error:
155+
for suite in suites_data:
156+
if suite["name"] == suite_name:
164157
suite_id = suite["id"]
165158
self.data_provider.update_data([{"suite_id": suite["id"], "name": suite["name"]}])
166-
except Exception as e:
167-
error_message = f"Error parsing suites response: {e}"
159+
break
160+
return (
161+
(suite_id, "")
162+
if suite_id != -1
163+
else (-1, FAULT_MAPPING["missing_suite"].format(suite_name=suite_name))
164+
)
168165
else:
169-
error_message = response.error_message
170-
171-
return suite_id, error_message
166+
return -1, error
172167

173168
def get_suite_ids(self, project_id: int) -> Tuple[List[int], str]:
174169
"""Get suite IDs for requested project_id.
175170
: project_id: project id
176171
: returns: tuple with list of suite ids and error string"""
177172
available_suites = []
178173
returned_resources = []
179-
error_message = ""
180-
response = self.client.send_get(f"get_suites/{project_id}")
181-
if not response.error_message:
182-
try:
183-
parsed = json.loads(response.response_text) if isinstance(response.response_text, str) else response.response_text
184-
suite_list = parsed.get("suites") if isinstance(parsed, dict) else parsed
185-
for suite in suite_list:
186-
available_suites.append(int(suite["id"]))
187-
returned_resources.append({
174+
suites_data, error = self.__get_all_suites(project_id)
175+
if not error:
176+
for suite in suites_data:
177+
available_suites.append(suite["id"])
178+
returned_resources.append(
179+
{
188180
"suite_id": suite["id"],
189181
"name": suite["name"],
190-
})
191-
except Exception as e:
192-
error_message = f"Error parsing suites response: {e}"
193-
else:
194-
error_message = response.error_message
195-
196-
if returned_resources:
197-
self.data_provider.update_data(suite_data=returned_resources)
182+
}
183+
)
184+
if returned_resources:
185+
self.data_provider.update_data(suite_data=returned_resources)
186+
else:
187+
print("Update skipped")
188+
return (
189+
(available_suites, "")
190+
if len(available_suites) > 0
191+
else ([], FAULT_MAPPING["no_suites_found"].format(project_id=project_id))
192+
)
198193
else:
199-
print("Update skipped")
200-
201-
return available_suites, error_message
194+
return [], error
202195

203196
def add_suites(self, project_id: int) -> Tuple[List[Dict], str]:
204197
"""
@@ -707,10 +700,16 @@ def __get_all_tests_in_run(self, run_id=None) -> Tuple[List[dict], str]:
707700

708701
def __get_all_projects(self) -> Tuple[List[dict], str]:
709702
"""
710-
Get all cases from all pages
703+
Get all projects from all pages
711704
"""
712705
return self.__get_all_entities('projects', f"get_projects")
713706

707+
def __get_all_suites(self, project_id) -> Tuple[List[dict], str]:
708+
"""
709+
Get all suites from all pages
710+
"""
711+
return self.__get_all_entities('suites', f"get_suites/{project_id}")
712+
714713
def __get_all_entities(self, entity: str, link=None, entities=[]) -> Tuple[List[Dict], str]:
715714
"""
716715
Get all entities from all pages if number of entities is too big to return in single response.

trcli/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
proxy_bypass_error= "Failed to bypass the proxy for host. Please check the settings.",
6464
proxy_invalid_configuration= "The provided proxy configuration is invalid. Please check the proxy URL and format.",
6565
ssl_error_on_proxy= "SSL error encountered while using the HTTPS proxy. Please check the proxy's SSL certificate.",
66-
no_proxy_match_error= "The host {host} does not match any NO_PROXY rules. Ensure the correct domains or IP addresses are specified for bypassing the proxy."
66+
no_proxy_match_error= "The host {host} does not match any NO_PROXY rules. Ensure the correct domains or IP addresses are specified for bypassing the proxy.",
67+
no_suites_found= "The project {project_id} does not have any suites."
6768
)
6869

6970
COMMAND_FAULT_MAPPING = dict(

0 commit comments

Comments
 (0)