testit-cli 2.8.4__tar.gz → 2.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testit_cli-2.8.4 → testit_cli-2.9.0}/PKG-INFO +2 -2
- {testit_cli-2.8.4 → testit_cli-2.9.0}/setup.py +2 -2
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/apiclient.py +102 -12
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/click_commands.py +144 -8
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/converter.py +11 -5
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/importer.py +5 -2
- testit_cli-2.9.0/src/testit_cli/models/config.py +35 -0
- testit_cli-2.9.0/src/testit_cli/models/status_type.py +9 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/models/testcase.py +14 -8
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/parser.py +5 -3
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/service.py +26 -0
- testit_cli-2.9.0/src/testit_cli/validation.py +29 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/PKG-INFO +2 -2
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/SOURCES.txt +1 -1
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/requires.txt +1 -1
- testit_cli-2.9.0/tests/test_click.py +59 -0
- testit_cli-2.9.0/tests/test_service.py +105 -0
- testit_cli-2.8.4/src/testit_cli/models/config.py +0 -22
- testit_cli-2.8.4/src/testit_cli/models/status.py +0 -7
- testit_cli-2.8.4/src/testit_cli/validation.py +0 -20
- testit_cli-2.8.4/tests/test_click.py +0 -136
- testit_cli-2.8.4/tests/test_service.py +0 -79
- {testit_cli-2.8.4 → testit_cli-2.9.0}/LICENSE +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/README.md +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/setup.cfg +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/__init__.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/__main__.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/autotests_filter.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/dir_worker.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/file_worker.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/filter_factory.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/html_escape_utils.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/logger.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/models/__init__.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/models/mode.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/models/testrun.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli/service_factory.py +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/dependency_links.txt +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/entry_points.txt +0 -0
- {testit_cli-2.8.4 → testit_cli-2.9.0}/src/testit_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testit-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: This tool is the command line wrapper of Test IT allowing you to upload the test results in real time to Test IT
|
|
5
5
|
Home-page: https://pypi.org/project/testit-cli/
|
|
6
6
|
Author: Integration team
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: testit-api-client==7.5.
|
|
19
|
+
Requires-Dist: testit-api-client==7.5.5
|
|
20
20
|
Requires-Dist: validators
|
|
21
21
|
Requires-Dist: tqdm
|
|
22
22
|
Requires-Dist: click~=8.0.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from setuptools import find_packages, setup
|
|
2
2
|
|
|
3
|
-
VERSION = "2.
|
|
3
|
+
VERSION = "2.9.0"
|
|
4
4
|
|
|
5
5
|
setup(
|
|
6
6
|
name='testit-cli',
|
|
@@ -26,7 +26,7 @@ setup(
|
|
|
26
26
|
py_modules=['testit_cli'],
|
|
27
27
|
packages=find_packages(where='src'),
|
|
28
28
|
package_dir={'': 'src'},
|
|
29
|
-
install_requires=['testit-api-client==7.5.
|
|
29
|
+
install_requires=['testit-api-client==7.5.5', 'validators', 'tqdm', 'click~=8.0.4'],
|
|
30
30
|
entry_points={
|
|
31
31
|
'console_scripts': [
|
|
32
32
|
'testit = testit_cli.__main__:console_main'
|
|
@@ -5,18 +5,29 @@ import typing
|
|
|
5
5
|
|
|
6
6
|
from testit_api_client import ApiClient as TmsClient
|
|
7
7
|
from testit_api_client import Configuration
|
|
8
|
-
from testit_api_client.apis import AttachmentsApi, AutoTestsApi, TestRunsApi, TestResultsApi
|
|
9
|
-
from testit_api_client.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
from testit_api_client.apis import AttachmentsApi, AutoTestsApi, TestRunsApi, TestResultsApi, ProjectsApi, WorkflowsApi
|
|
9
|
+
from testit_api_client.models import (
|
|
10
|
+
ApiV2AutoTestsSearchPostRequest,
|
|
11
|
+
ApiV2TestResultsSearchPostRequest,
|
|
12
|
+
ApiV2TestRunsIdRerunsPostRequest,
|
|
13
|
+
AttachmentModel,
|
|
14
|
+
AttachmentPutModel,
|
|
15
|
+
AutoTestModel,
|
|
16
|
+
AutoTestResultsForTestRunModel,
|
|
17
|
+
CreateAutoTestRequest,
|
|
18
|
+
ManualRerunApiResult,
|
|
19
|
+
ManualRerunSelectTestResultsApiModelExtractionModel,
|
|
20
|
+
ManualRerunSelectTestResultsApiModelFilter,
|
|
21
|
+
TestRunV2ApiResult,
|
|
22
|
+
UpdateAutoTestRequest,
|
|
23
|
+
UpdateEmptyRequest,
|
|
24
|
+
TestResultShortResponse,
|
|
25
|
+
CreateEmptyRequest,
|
|
26
|
+
AutoTestApiResult,
|
|
27
|
+
ProjectModel,
|
|
28
|
+
WorkflowApiResult,
|
|
29
|
+
CreateProjectRequest,
|
|
30
|
+
)
|
|
20
31
|
|
|
21
32
|
from .converter import Converter
|
|
22
33
|
from .models.testrun import TestRun
|
|
@@ -40,6 +51,8 @@ class ApiClient:
|
|
|
40
51
|
self.__autotest_api = AutoTestsApi(api_client=client)
|
|
41
52
|
self.__attachments_api = AttachmentsApi(api_client=client)
|
|
42
53
|
self.__test_results_api = TestResultsApi(api_client=client)
|
|
54
|
+
self.__projects_api = ProjectsApi(api_client=client)
|
|
55
|
+
self.__workflows_api = WorkflowsApi(api_client=client)
|
|
43
56
|
|
|
44
57
|
def create_test_run(self, project_id: str, name: str) -> TestRun:
|
|
45
58
|
"""Function creates test run and returns test run id."""
|
|
@@ -54,6 +67,55 @@ class ApiClient:
|
|
|
54
67
|
|
|
55
68
|
return Converter.test_run_v2_get_model_to_test_run(test_run)
|
|
56
69
|
|
|
70
|
+
def rerun_test_run(self, test_run_id: str,
|
|
71
|
+
configuration_ids: list[str] = None,
|
|
72
|
+
status_codes: list[str] = None,
|
|
73
|
+
failure_categories: list[str] = None,
|
|
74
|
+
namespace: str = None,
|
|
75
|
+
class_name: str = None,
|
|
76
|
+
auto_test_global_ids: list[int] = None,
|
|
77
|
+
auto_test_tags: list[str] = None,
|
|
78
|
+
exclude_auto_test_tags: list[str] = None,
|
|
79
|
+
auto_test_name: str = None,
|
|
80
|
+
test_result_ids: list[str] = None,
|
|
81
|
+
webhook_ids: list[str] = None) -> None:
|
|
82
|
+
"""Function reruns test run and returns manual rerun result."""
|
|
83
|
+
filter_model = ManualRerunSelectTestResultsApiModelFilter(
|
|
84
|
+
configuration_ids=configuration_ids,
|
|
85
|
+
status_codes=status_codes,
|
|
86
|
+
failure_categories=failure_categories,
|
|
87
|
+
namespace=namespace,
|
|
88
|
+
class_name=class_name,
|
|
89
|
+
auto_test_global_ids=auto_test_global_ids,
|
|
90
|
+
auto_test_tags=auto_test_tags,
|
|
91
|
+
exclude_auto_test_tags=exclude_auto_test_tags,
|
|
92
|
+
name=auto_test_name
|
|
93
|
+
) if any(param is not None for param in [
|
|
94
|
+
configuration_ids, status_codes, failure_categories,
|
|
95
|
+
namespace, class_name, auto_test_global_ids, auto_test_tags,
|
|
96
|
+
exclude_auto_test_tags, auto_test_name
|
|
97
|
+
]) else None
|
|
98
|
+
|
|
99
|
+
extraction_model = ManualRerunSelectTestResultsApiModelExtractionModel(
|
|
100
|
+
test_result_ids=test_result_ids
|
|
101
|
+
) if test_result_ids is not None else None
|
|
102
|
+
|
|
103
|
+
model = ApiV2TestRunsIdRerunsPostRequest(
|
|
104
|
+
filter=filter_model,
|
|
105
|
+
extraction_model=extraction_model,
|
|
106
|
+
webhook_ids=webhook_ids
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
logging.debug(f"Rerunning test run {test_run_id} with model: {model}")
|
|
110
|
+
|
|
111
|
+
result: ManualRerunApiResult = self.__test_run_api.api_v2_test_runs_id_reruns_post(
|
|
112
|
+
id=test_run_id,
|
|
113
|
+
api_v2_test_runs_id_reruns_post_request=model
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
logging.info(f'Reran testrun (ID: {test_run_id})\nTest results count: {result.test_results_count}')
|
|
117
|
+
logging.debug(f"Test run rerun result: {result}")
|
|
118
|
+
|
|
57
119
|
def update_test_run(self, test_run: TestRun) -> None:
|
|
58
120
|
"""Function updates test run."""
|
|
59
121
|
model: UpdateEmptyRequest = Converter.test_run_to_update_empty_request(test_run)
|
|
@@ -178,3 +240,31 @@ class ApiClient:
|
|
|
178
240
|
logging.debug(f"Got test results: {test_results}")
|
|
179
241
|
|
|
180
242
|
return test_results
|
|
243
|
+
|
|
244
|
+
def __get_project(self, project_id: str) -> ProjectModel:
|
|
245
|
+
"""Function returns ProjectModel."""
|
|
246
|
+
return self.__projects_api.get_project_by_id(id=project_id)
|
|
247
|
+
|
|
248
|
+
def __get_workflow_by_id(self, workflow_id: str) -> WorkflowApiResult:
|
|
249
|
+
"""Function returns WorkflowApiResult."""
|
|
250
|
+
return self.__workflows_api.api_v2_workflows_id_get(id=workflow_id)
|
|
251
|
+
|
|
252
|
+
def get_status_codes(self, project_id: str) -> typing.List[str]:
|
|
253
|
+
"""Function returns list of statuses from project."""
|
|
254
|
+
project: ProjectModel = self.__get_project(project_id)
|
|
255
|
+
workflow: WorkflowApiResult = self.__get_workflow_by_id(project.workflow_id)
|
|
256
|
+
|
|
257
|
+
return [status.code for status in workflow.statuses]
|
|
258
|
+
|
|
259
|
+
def create_project(self, name: str, description: str = None, is_favorite: bool = None, workflow_id: str = None) -> str:
|
|
260
|
+
"""Function creates project and returns project id."""
|
|
261
|
+
model = CreateProjectRequest(name=name, description=description, is_favorite=is_favorite, workflow_id=workflow_id)
|
|
262
|
+
model = HtmlEscapeUtils.escape_html_in_object(model)
|
|
263
|
+
logging.debug(f"Creating project with model: {model}")
|
|
264
|
+
|
|
265
|
+
project = self.__projects_api.create_project(create_project_request=model)
|
|
266
|
+
|
|
267
|
+
logging.info(f'Created new project (ID: {project.id})')
|
|
268
|
+
logging.debug(f"Project created: {project}")
|
|
269
|
+
|
|
270
|
+
return str(project.id)
|
|
@@ -76,7 +76,20 @@ class ExtraArgsForOption(click.Option):
|
|
|
76
76
|
@click.option("-iff", "--ignore-flaky-failure", is_flag=True, help="Ignore status flakyFailure in results")
|
|
77
77
|
def upload_results(url, token, configuration_id, testrun_id, separator, namespace, classname, results, debug, attachments, disable_cert_validation, ignore_flaky_failure):
|
|
78
78
|
"""Uploading results from different streams"""
|
|
79
|
-
config = Config(
|
|
79
|
+
config = Config(
|
|
80
|
+
url=url,
|
|
81
|
+
token=token,
|
|
82
|
+
configuration_id=configuration_id,
|
|
83
|
+
testrun_id=testrun_id,
|
|
84
|
+
separator=separator,
|
|
85
|
+
namespace=namespace,
|
|
86
|
+
classname=classname,
|
|
87
|
+
results=list(chain.from_iterable(results)),
|
|
88
|
+
is_debug=debug,
|
|
89
|
+
paths_to_attachments=list(chain.from_iterable(attachments)),
|
|
90
|
+
disable_cert_validation=disable_cert_validation,
|
|
91
|
+
ignore_flaky_failure=ignore_flaky_failure,
|
|
92
|
+
)
|
|
80
93
|
service = ServiceFactory().get(config)
|
|
81
94
|
|
|
82
95
|
service.upload_results()
|
|
@@ -103,9 +116,22 @@ def import_results(url, token, project_id, configuration_id, testrun_id, testrun
|
|
|
103
116
|
if testrun_id is not None and testrun_name is not None:
|
|
104
117
|
click.echo("Illegal usage: `{}` are mutually exclusive arguments.".format(', '.join(["--testrun-id", "--testrun-name"])), err=True)
|
|
105
118
|
|
|
106
|
-
config = Config(
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
config = Config(
|
|
120
|
+
url=url,
|
|
121
|
+
token=token,
|
|
122
|
+
project_id=project_id,
|
|
123
|
+
configuration_id=configuration_id,
|
|
124
|
+
testrun_id=testrun_id,
|
|
125
|
+
testrun_name=testrun_name,
|
|
126
|
+
separator=separator,
|
|
127
|
+
namespace=namespace,
|
|
128
|
+
classname=classname,
|
|
129
|
+
results=list(chain.from_iterable(results)),
|
|
130
|
+
is_debug=debug,
|
|
131
|
+
paths_to_attachments=list(chain.from_iterable(attachments)),
|
|
132
|
+
disable_cert_validation=disable_cert_validation,
|
|
133
|
+
ignore_flaky_failure=ignore_flaky_failure,
|
|
134
|
+
)
|
|
109
135
|
service = ServiceFactory().get(config)
|
|
110
136
|
|
|
111
137
|
service.import_results()
|
|
@@ -128,7 +154,16 @@ def testrun():
|
|
|
128
154
|
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
129
155
|
def create_test_run(url, token, project_id, testrun_name, output, debug, attachments, disable_cert_validation):
|
|
130
156
|
"""Creating a new test run"""
|
|
131
|
-
config = Config(
|
|
157
|
+
config = Config(
|
|
158
|
+
url=url,
|
|
159
|
+
token=token,
|
|
160
|
+
project_id=project_id,
|
|
161
|
+
testrun_name=testrun_name,
|
|
162
|
+
is_debug=debug,
|
|
163
|
+
output=output,
|
|
164
|
+
paths_to_attachments=list(chain.from_iterable(attachments)),
|
|
165
|
+
disable_cert_validation=disable_cert_validation,
|
|
166
|
+
)
|
|
132
167
|
service = ServiceFactory().get(config)
|
|
133
168
|
|
|
134
169
|
service.create_test_run()
|
|
@@ -143,7 +178,14 @@ def create_test_run(url, token, project_id, testrun_name, output, debug, attachm
|
|
|
143
178
|
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
144
179
|
def complete_test_run(url, token, testrun_id, debug, attachments, disable_cert_validation):
|
|
145
180
|
"""Completing the test run"""
|
|
146
|
-
config = Config(
|
|
181
|
+
config = Config(
|
|
182
|
+
url=url,
|
|
183
|
+
token=token,
|
|
184
|
+
testrun_id=testrun_id,
|
|
185
|
+
is_debug=debug,
|
|
186
|
+
paths_to_attachments=list(chain.from_iterable(attachments)),
|
|
187
|
+
disable_cert_validation=disable_cert_validation,
|
|
188
|
+
)
|
|
147
189
|
service = ServiceFactory().get(config)
|
|
148
190
|
|
|
149
191
|
service.finished_test_run()
|
|
@@ -158,12 +200,63 @@ def complete_test_run(url, token, testrun_id, debug, attachments, disable_cert_v
|
|
|
158
200
|
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
159
201
|
def upload_attachments_for_test_run(url, token, testrun_id, debug, attachments, disable_cert_validation):
|
|
160
202
|
"""Uploading attachments for the test run"""
|
|
161
|
-
config = Config(
|
|
203
|
+
config = Config(
|
|
204
|
+
url=url,
|
|
205
|
+
token=token,
|
|
206
|
+
testrun_id=testrun_id,
|
|
207
|
+
is_debug=debug,
|
|
208
|
+
paths_to_attachments=list(chain.from_iterable(attachments)),
|
|
209
|
+
disable_cert_validation=disable_cert_validation,
|
|
210
|
+
)
|
|
162
211
|
service = ServiceFactory().get(config)
|
|
163
212
|
|
|
164
213
|
service.upload_attachments_for_test_run()
|
|
165
214
|
|
|
166
215
|
|
|
216
|
+
@testrun.command("rerun")
|
|
217
|
+
@click.option("-u", "--url", type=str, envvar='TMS_URL', required=True, help="Set url address of the Test IT instance (https://demo.testit.software)", callback=validate_url)
|
|
218
|
+
@click.option("-t", "--token", type=str, envvar='TMS_TOKEN', required=True, help="Set API token (T2lKd2pLZGI4WHRhaVZUejNl)")
|
|
219
|
+
@click.option("-ti", "--testrun-id", type=str, envvar='TMS_TEST_RUN_ID', required=True, help="Set test run id (3802f329-190c-4617-8bb0-2c3696abeb8f)", callback=validate_uuid)
|
|
220
|
+
@click.option("-ci", "--configuration-id", type=str, envvar='TMS_CONFIGURATION_ID', multiple=True, help="Set configuration id (15dbb164-c1aa-4cbf-830c-8c01ae14f4fb)", callback=validate_uuid)
|
|
221
|
+
@click.option("-sc", "--status-code", type=str, multiple=True, help="Set status code for filtering test results")
|
|
222
|
+
@click.option("-fc", "--failure-category", type=str, multiple=True, help="Set failure category for filtering test results")
|
|
223
|
+
@click.option("-ns", "--namespace", type=str, help="Set namespace for filtering test results")
|
|
224
|
+
@click.option("-cn", "--classname", type=str, help="Set class name for filtering test results")
|
|
225
|
+
@click.option("-atgi", "--autotest-global-id", type=int, multiple=True, help="Set autotest global ID for filtering test results")
|
|
226
|
+
@click.option("-att", "--autotest-tag", type=str, multiple=True, help="Set autotest tag for filtering test results")
|
|
227
|
+
@click.option("-eatt", "--exclude-autotest-tag", type=str, multiple=True, help="Set autotest tag to exclude from filtering test results")
|
|
228
|
+
@click.option("-atn", "--autotest-name", type=str, help="Set autotest name for filtering test results")
|
|
229
|
+
@click.option("-tri", "--test-result-id", type=str, multiple=True, help="Set test result ID for extraction (UUID)", callback=validate_uuid)
|
|
230
|
+
@click.option("-wi", "--webhook-id", type=str, multiple=True, help="Set webhook ID for rerunning (UUID)", callback=validate_uuid)
|
|
231
|
+
@click.option("-d", "--debug", is_flag=True, help="Set debug logs")
|
|
232
|
+
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
233
|
+
def rerun_test_run(url, token, testrun_id, configuration_id, status_code, failure_category,
|
|
234
|
+
namespace, classname, autotest_global_id, autotest_tag, exclude_autotest_tag,
|
|
235
|
+
autotest_name, test_result_id, webhook_id, debug, disable_cert_validation):
|
|
236
|
+
"""Rerunning the test run"""
|
|
237
|
+
config = Config(
|
|
238
|
+
url=url,
|
|
239
|
+
token=token,
|
|
240
|
+
testrun_id=testrun_id,
|
|
241
|
+
is_debug=debug,
|
|
242
|
+
disable_cert_validation=disable_cert_validation,
|
|
243
|
+
configuration_ids=list(configuration_id) if configuration_id else None,
|
|
244
|
+
status_codes=list(status_code) if status_code else None,
|
|
245
|
+
failure_categories=list(failure_category) if failure_category else None,
|
|
246
|
+
namespace=namespace,
|
|
247
|
+
classname=classname,
|
|
248
|
+
auto_test_global_ids=list(autotest_global_id) if autotest_global_id else None,
|
|
249
|
+
auto_test_tags=list(autotest_tag) if autotest_tag else None,
|
|
250
|
+
exclude_auto_test_tags=list(exclude_autotest_tag) if exclude_autotest_tag else None,
|
|
251
|
+
auto_test_name=autotest_name,
|
|
252
|
+
test_result_ids=list(test_result_id) if test_result_id else None,
|
|
253
|
+
webhook_ids=list(webhook_id) if webhook_id else None,
|
|
254
|
+
)
|
|
255
|
+
service = ServiceFactory().get(config)
|
|
256
|
+
|
|
257
|
+
service.rerun_test_run()
|
|
258
|
+
|
|
259
|
+
|
|
167
260
|
PYTHON_FRAMEWORKS = ['pytest', 'robotframework', 'behave', 'nose']
|
|
168
261
|
JAVA_FRAMEWORKS = ['gradle-testng', 'gradle-junit5', 'gradle-junit4', 'gradle-cucumber', 'maven-testng', 'maven-junit5', 'maven-junit4', 'maven-cucumber']
|
|
169
262
|
KOTLIN_FRAMEWORKS = ['kotest']
|
|
@@ -184,7 +277,50 @@ SWIFT_FRAMEWORKS = ['xctest']
|
|
|
184
277
|
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
185
278
|
def create_filter_for_framework(url, token, configuration_id, testrun_id, framework, debug, output, disable_cert_validation):
|
|
186
279
|
"""Creating filter by autotests for test frameworks"""
|
|
187
|
-
config = Config(
|
|
280
|
+
config = Config(
|
|
281
|
+
url=url,
|
|
282
|
+
token=token,
|
|
283
|
+
configuration_id=configuration_id,
|
|
284
|
+
testrun_id=testrun_id,
|
|
285
|
+
is_debug=debug,
|
|
286
|
+
output=output,
|
|
287
|
+
disable_cert_validation=disable_cert_validation,
|
|
288
|
+
framework=framework,
|
|
289
|
+
)
|
|
188
290
|
service = ServiceFactory().get(config)
|
|
189
291
|
|
|
190
292
|
service.create_filter_for_test_framework()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@execute.group()
|
|
296
|
+
def project():
|
|
297
|
+
"""Working with projects"""
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@project.command("create")
|
|
302
|
+
@click.option("-u", "--url", type=str, envvar='TMS_URL', required=True, help="Set url address of the Test IT instance (https://demo.testit.software)", callback=validate_url)
|
|
303
|
+
@click.option("-t", "--token", type=str, envvar='TMS_TOKEN', required=True, help="Set API token (T2lKd2pLZGI4WHRhaVZUejNl)")
|
|
304
|
+
@click.option("-n", "--name", type=str, envvar='TMS_PROJECT_NAME', required=True, help="Set project name (Project01)")
|
|
305
|
+
@click.option("-d", "--description", type=str, help="Set project description (Description of the project)", default=None)
|
|
306
|
+
@click.option("-f", "--favorite", is_flag=True, help="Mark project as favorite")
|
|
307
|
+
@click.option("-wf", "--workflow-id", type=str, help="Set workflow id for the project", default=None)
|
|
308
|
+
@click.option("-o", "--output", type=str, required=True, help="Set file path for output (FILE)")
|
|
309
|
+
@click.option("-d", "--debug", is_flag=True, help="Set debug logs")
|
|
310
|
+
@click.option("-dcv", "--disable-cert-validation", is_flag=True, help="Disables certificate validation")
|
|
311
|
+
def create_project(url, token, name, description, favorite, workflow_id, output, debug, disable_cert_validation):
|
|
312
|
+
"""Creating a new project"""
|
|
313
|
+
config = Config(
|
|
314
|
+
url=url,
|
|
315
|
+
token=token,
|
|
316
|
+
project_name=name,
|
|
317
|
+
project_description=description,
|
|
318
|
+
project_is_favorite=favorite,
|
|
319
|
+
project_workflow_id=workflow_id,
|
|
320
|
+
is_debug=debug,
|
|
321
|
+
output=output,
|
|
322
|
+
disable_cert_validation=disable_cert_validation,
|
|
323
|
+
)
|
|
324
|
+
service = ServiceFactory().get(config)
|
|
325
|
+
|
|
326
|
+
service.create_project()
|
|
@@ -15,7 +15,8 @@ from testit_api_client.models import (
|
|
|
15
15
|
AutoTestResultsForTestRunModel,
|
|
16
16
|
AttachmentPutModel,
|
|
17
17
|
LinkPutModel,
|
|
18
|
-
|
|
18
|
+
TestStatusType,
|
|
19
|
+
TestStatusApiType,
|
|
19
20
|
TestResultShortResponse,
|
|
20
21
|
)
|
|
21
22
|
from testit_api_client.model.assign_attachment_api_model import AssignAttachmentApiModel
|
|
@@ -58,7 +59,7 @@ class Converter:
|
|
|
58
59
|
return ApiV2TestResultsSearchPostRequest(
|
|
59
60
|
test_run_ids=[testrun_id],
|
|
60
61
|
configuration_ids=[configuration_id],
|
|
61
|
-
|
|
62
|
+
status_types=[TestStatusApiType("InProgress")])
|
|
62
63
|
|
|
63
64
|
@staticmethod
|
|
64
65
|
def test_result_short_get_models_to_autotest_ids(
|
|
@@ -110,17 +111,22 @@ class Converter:
|
|
|
110
111
|
|
|
111
112
|
@staticmethod
|
|
112
113
|
def test_result_to_testrun_result_post_model(
|
|
113
|
-
result: TestCase, external_id: str, configuration_id: str
|
|
114
|
+
result: TestCase, external_id: str, configuration_id: str, status_codes: typing.List[str]
|
|
114
115
|
) -> AutoTestResultsForTestRunModel:
|
|
115
|
-
|
|
116
|
+
model = AutoTestResultsForTestRunModel(
|
|
116
117
|
configuration_id=configuration_id,
|
|
117
118
|
auto_test_external_id=external_id,
|
|
118
|
-
|
|
119
|
+
status_type=TestStatusType(result.get_status_type()),
|
|
119
120
|
traces=result.get_trace(),
|
|
120
121
|
duration=round(result.get_duration()),
|
|
121
122
|
message=result.get_message(),
|
|
122
123
|
)
|
|
123
124
|
|
|
125
|
+
if result.get_status().upper() in status_codes:
|
|
126
|
+
model.status_code = result.get_status()
|
|
127
|
+
|
|
128
|
+
return model
|
|
129
|
+
|
|
124
130
|
@classmethod
|
|
125
131
|
def test_run_v2_get_model_to_test_run(cls, test_run_model: TestRunV2ApiResult) -> TestRun:
|
|
126
132
|
return TestRun(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
|
+
import typing
|
|
2
3
|
|
|
3
4
|
from testit_api_client.model.api_v2_auto_tests_search_post_request import ApiV2AutoTestsSearchPostRequest
|
|
4
5
|
from testit_api_client.model.auto_test_api_result import AutoTestApiResult
|
|
@@ -15,7 +16,9 @@ class Importer:
|
|
|
15
16
|
self.__api_client = api_client
|
|
16
17
|
self.__config = config
|
|
17
18
|
|
|
18
|
-
def send_results(self, results:
|
|
19
|
+
def send_results(self, results: typing.List[TestCase]) -> None:
|
|
20
|
+
status_codes = self.__api_client.get_status_codes(self.__config.project_id)
|
|
21
|
+
|
|
19
22
|
for result in tqdm(results, desc="Uploading"):
|
|
20
23
|
external_id = self.__get_external_id(
|
|
21
24
|
result.get_name_space()
|
|
@@ -46,7 +49,7 @@ class Importer:
|
|
|
46
49
|
self.__api_client.send_test_result(
|
|
47
50
|
self.__config.testrun_id,
|
|
48
51
|
Converter.test_result_to_testrun_result_post_model(
|
|
49
|
-
result, external_id, self.__config.configuration_id
|
|
52
|
+
result, external_id, self.__config.configuration_id, status_codes
|
|
50
53
|
),
|
|
51
54
|
)
|
|
52
55
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Config:
|
|
7
|
+
url: str
|
|
8
|
+
token: str
|
|
9
|
+
project_id: str = ""
|
|
10
|
+
project_name: str = ""
|
|
11
|
+
project_description: typing.Optional[str] = None
|
|
12
|
+
project_is_favorite: bool = False
|
|
13
|
+
project_workflow_id: typing.Optional[str] = None
|
|
14
|
+
configuration_id: str = ""
|
|
15
|
+
configuration_ids: typing.Optional[list[str]] = None
|
|
16
|
+
testrun_id: typing.Optional[str] = None
|
|
17
|
+
testrun_name: typing.Optional[str] = None
|
|
18
|
+
separator: typing.Optional[str] = None
|
|
19
|
+
namespace: typing.Optional[str] = None
|
|
20
|
+
classname: typing.Optional[str] = None
|
|
21
|
+
status_codes: typing.Optional[list[str]] = None
|
|
22
|
+
failure_categories: typing.Optional[list[str]] = None
|
|
23
|
+
auto_test_global_ids: typing.Optional[list[int]] = None
|
|
24
|
+
auto_test_tags: typing.Optional[list[str]] = None
|
|
25
|
+
exclude_auto_test_tags: typing.Optional[list[str]] = None
|
|
26
|
+
auto_test_name: typing.Optional[str] = None
|
|
27
|
+
test_result_ids: typing.Optional[list[str]] = None
|
|
28
|
+
webhook_ids: typing.Optional[list[str]] = None
|
|
29
|
+
results: list[typing.Any] = field(default_factory=list)
|
|
30
|
+
is_debug: bool = False
|
|
31
|
+
output: str = ""
|
|
32
|
+
paths_to_attachments: list[typing.Any] = field(default_factory=list)
|
|
33
|
+
disable_cert_validation: bool = False
|
|
34
|
+
framework: str = ""
|
|
35
|
+
ignore_flaky_failure: bool = False
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
from .status import Status
|
|
4
|
+
from .status_type import StatusType
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class TestCase:
|
|
@@ -11,7 +9,8 @@ class TestCase:
|
|
|
11
9
|
__name_space: Optional[str] = None
|
|
12
10
|
__class_name: Optional[str] = None
|
|
13
11
|
__duration: Optional[timedelta] = None
|
|
14
|
-
__status: Optional[
|
|
12
|
+
__status: Optional[str] = None
|
|
13
|
+
__status_type: Optional[StatusType] = None
|
|
15
14
|
__message: Optional[str] = None
|
|
16
15
|
__trace: Optional[str] = None
|
|
17
16
|
__is_flaky: Optional[bool] = None
|
|
@@ -21,7 +20,8 @@ class TestCase:
|
|
|
21
20
|
self.__name_space = name_space
|
|
22
21
|
self.__class_name = class_name
|
|
23
22
|
self.__duration = timedelta(seconds=float(duration))
|
|
24
|
-
self.__status =
|
|
23
|
+
self.__status = "passed"
|
|
24
|
+
self.__status_type = StatusType.SUCCEEDED
|
|
25
25
|
self.__trace = ""
|
|
26
26
|
|
|
27
27
|
def get_name(self) -> Optional[str]:
|
|
@@ -48,12 +48,18 @@ class TestCase:
|
|
|
48
48
|
def set_trace(self, value: str) -> None:
|
|
49
49
|
self.__trace = value
|
|
50
50
|
|
|
51
|
-
def get_status(self) ->
|
|
52
|
-
return
|
|
51
|
+
def get_status(self) -> str:
|
|
52
|
+
return self.__status
|
|
53
53
|
|
|
54
|
-
def set_status(self, value:
|
|
54
|
+
def set_status(self, value: str) -> None:
|
|
55
55
|
self.__status = value
|
|
56
56
|
|
|
57
|
+
def get_status_type(self) -> StatusType:
|
|
58
|
+
return self.__status_type
|
|
59
|
+
|
|
60
|
+
def set_status_type(self, value: StatusType) -> None:
|
|
61
|
+
self.__status_type = value
|
|
62
|
+
|
|
57
63
|
def get_is_flaky(self) -> Optional[bool]:
|
|
58
64
|
return self.__is_flaky
|
|
59
65
|
|
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
from xml.dom import minidom
|
|
4
4
|
|
|
5
5
|
from .models.config import Config
|
|
6
|
-
from .models.
|
|
6
|
+
from .models.status_type import StatusType
|
|
7
7
|
from .models.testcase import TestCase
|
|
8
8
|
from .file_worker import FileWorker
|
|
9
9
|
|
|
@@ -64,11 +64,13 @@ class Parser:
|
|
|
64
64
|
|
|
65
65
|
if elem.childNodes is not None:
|
|
66
66
|
for child in elem.childNodes:
|
|
67
|
+
testcase.set_status(child.nodeName)
|
|
68
|
+
|
|
67
69
|
if child.attributes and self.__MESSAGE_ATTRIBUTE_NAME in child.attributes:
|
|
68
70
|
testcase.set_message(child.attributes[self.__MESSAGE_ATTRIBUTE_NAME].value)
|
|
69
71
|
|
|
70
72
|
if child.nodeName == "skipped":
|
|
71
|
-
testcase.
|
|
73
|
+
testcase.set_status_type(StatusType.INCOMPLETE)
|
|
72
74
|
continue
|
|
73
75
|
|
|
74
76
|
# if in failure node names and not flaky
|
|
@@ -79,7 +81,7 @@ class Parser:
|
|
|
79
81
|
if len(child.childNodes) == 0 and len(child.attributes) == 0:
|
|
80
82
|
continue
|
|
81
83
|
|
|
82
|
-
testcase.
|
|
84
|
+
testcase.set_status_type(StatusType.FAILED)
|
|
83
85
|
|
|
84
86
|
testcase.set_trace(
|
|
85
87
|
testcase.get_trace() + self.__form_trace(child.childNodes))
|
|
@@ -43,6 +43,22 @@ class Service:
|
|
|
43
43
|
|
|
44
44
|
self.__write_to_output(test_run.id)
|
|
45
45
|
|
|
46
|
+
def rerun_test_run(self) -> None:
|
|
47
|
+
self.__api_client.rerun_test_run(
|
|
48
|
+
self.__config.testrun_id,
|
|
49
|
+
configuration_ids=self.__config.configuration_ids,
|
|
50
|
+
status_codes=self.__config.status_codes,
|
|
51
|
+
failure_categories=self.__config.failure_categories,
|
|
52
|
+
namespace=self.__config.namespace,
|
|
53
|
+
class_name=self.__config.classname,
|
|
54
|
+
auto_test_global_ids=self.__config.auto_test_global_ids,
|
|
55
|
+
auto_test_tags=self.__config.auto_test_tags,
|
|
56
|
+
exclude_auto_test_tags=self.__config.exclude_auto_test_tags,
|
|
57
|
+
auto_test_name=self.__config.auto_test_name,
|
|
58
|
+
test_result_ids=self.__config.test_result_ids,
|
|
59
|
+
webhook_ids=self.__config.webhook_ids
|
|
60
|
+
)
|
|
61
|
+
|
|
46
62
|
def finished_test_run(self):
|
|
47
63
|
test_run = self.__api_client.get_test_run(self.__config.testrun_id)
|
|
48
64
|
self.__update_test_run_with_attachments(test_run)
|
|
@@ -103,3 +119,13 @@ class Service:
|
|
|
103
119
|
autotests_filter = self.__autotests_filter.create_filter()
|
|
104
120
|
|
|
105
121
|
self.__write_to_output(autotests_filter)
|
|
122
|
+
|
|
123
|
+
def create_project(self):
|
|
124
|
+
project_id = self.__api_client.create_project(
|
|
125
|
+
self.__config.project_name,
|
|
126
|
+
self.__config.project_description,
|
|
127
|
+
self.__config.project_is_favorite,
|
|
128
|
+
self.__config.project_workflow_id
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.__write_to_output(project_id)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import validators
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def validate_uuid(ctx, param, value):
|
|
6
|
+
if value is None:
|
|
7
|
+
return value
|
|
8
|
+
if isinstance(value, (tuple, list)):
|
|
9
|
+
if len(value) == 0:
|
|
10
|
+
return value
|
|
11
|
+
for item in value:
|
|
12
|
+
if item is not None and not validators.uuid(item):
|
|
13
|
+
raise click.BadParameter(
|
|
14
|
+
f'"{item}" uuid of {param.name} (3802f329-190c-4617-8bb0-2c3696abeb8f)'
|
|
15
|
+
)
|
|
16
|
+
return value
|
|
17
|
+
if not validators.uuid(value):
|
|
18
|
+
raise click.BadParameter(f'"{value}" uuid of {param.name} (3802f329-190c-4617-8bb0-2c3696abeb8f)')
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_url(ctx, param, value):
|
|
23
|
+
if not validators.url(value):
|
|
24
|
+
raise click.BadParameter(f'"{value}" url address of the Test IT instance (https://demo.testit.software)')
|
|
25
|
+
|
|
26
|
+
if value.endswith("/"):
|
|
27
|
+
return value[:-1]
|
|
28
|
+
|
|
29
|
+
return value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testit-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: This tool is the command line wrapper of Test IT allowing you to upload the test results in real time to Test IT
|
|
5
5
|
Home-page: https://pypi.org/project/testit-cli/
|
|
6
6
|
Author: Integration team
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: testit-api-client==7.5.
|
|
19
|
+
Requires-Dist: testit-api-client==7.5.5
|
|
20
20
|
Requires-Dist: validators
|
|
21
21
|
Requires-Dist: tqdm
|
|
22
22
|
Requires-Dist: click~=8.0.4
|
|
@@ -26,7 +26,7 @@ src/testit_cli.egg-info/top_level.txt
|
|
|
26
26
|
src/testit_cli/models/__init__.py
|
|
27
27
|
src/testit_cli/models/config.py
|
|
28
28
|
src/testit_cli/models/mode.py
|
|
29
|
-
src/testit_cli/models/
|
|
29
|
+
src/testit_cli/models/status_type.py
|
|
30
30
|
src/testit_cli/models/testcase.py
|
|
31
31
|
src/testit_cli/models/testrun.py
|
|
32
32
|
tests/test_click.py
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tests.cli_support import (
|
|
4
|
+
assert_exit_code,
|
|
5
|
+
assert_help_ok,
|
|
6
|
+
assert_invalid_parameter_value,
|
|
7
|
+
assert_missing_required_option,
|
|
8
|
+
invoke,
|
|
9
|
+
)
|
|
10
|
+
from tests.helper import Helper
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture()
|
|
14
|
+
def runner():
|
|
15
|
+
from click.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
return CliRunner()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_root_help_lists_known_commands(runner):
|
|
21
|
+
result = invoke(runner, [])
|
|
22
|
+
assert_help_ok(result, "Commands:")
|
|
23
|
+
for line in Helper.root_help_command_lines():
|
|
24
|
+
assert line in result.output
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("argv, unknown_token", Helper.unknown_subcommand_cases())
|
|
28
|
+
def test_unknown_command_reports_error(runner, argv, unknown_token):
|
|
29
|
+
result = invoke(runner, argv)
|
|
30
|
+
assert_exit_code(result, 2)
|
|
31
|
+
assert f"No such command '{unknown_token}'" in result.output
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize("argv, command_lines", Helper.subgroup_help_cases())
|
|
35
|
+
def test_subgroup_help_lists_nested_commands(runner, argv, command_lines):
|
|
36
|
+
result = invoke(runner, argv)
|
|
37
|
+
assert_help_ok(result, "Commands:")
|
|
38
|
+
for fragment in command_lines:
|
|
39
|
+
assert fragment in result.output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("argv, missing_long_option", Helper.missing_required_option_cases())
|
|
43
|
+
def test_missing_required_option(runner, argv, missing_long_option):
|
|
44
|
+
assert_missing_required_option(invoke(runner, argv), missing_long_option)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize("argv", Helper.invalid_option_cases())
|
|
48
|
+
def test_unrecognized_option_exits_with_usage_error(runner, argv):
|
|
49
|
+
assert_exit_code(invoke(runner, argv), 2)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize("argv, doc_fragment", Helper.command_help_doc_cases())
|
|
53
|
+
def test_command_help_includes_description(runner, argv, doc_fragment):
|
|
54
|
+
assert_help_ok(invoke(runner, argv), doc_fragment)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("argv, option_hint", Helper.invalid_rerun_uuid_multi_option_cases())
|
|
58
|
+
def test_rerun_rejects_invalid_uuid_for_optional_multi_options(runner, argv, option_hint):
|
|
59
|
+
assert_invalid_parameter_value(invoke(runner, argv), option_hint)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from unittest.mock import Mock
|
|
6
|
+
|
|
7
|
+
from src.testit_cli.service import Service
|
|
8
|
+
from src.testit_cli.models.config import Config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_config():
|
|
13
|
+
return Mock(spec=Config)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def api_client():
|
|
18
|
+
return Mock()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def service(mock_config, api_client):
|
|
23
|
+
return Service(
|
|
24
|
+
config=mock_config,
|
|
25
|
+
api_client=api_client,
|
|
26
|
+
parser=Mock(),
|
|
27
|
+
importer=Mock(),
|
|
28
|
+
autotests_filter=Mock(),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _assert_utf8_file_equals(path: str, expected: str) -> None:
|
|
33
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
34
|
+
assert f.read() == expected
|
|
35
|
+
with open(path, "rb") as f:
|
|
36
|
+
assert f.read().decode("utf-8") == expected
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_write_to_output_uses_utf8_encoding(service, mock_config):
|
|
40
|
+
# Deliberate non-ASCII payload to assert UTF-8 read/write round-trip.
|
|
41
|
+
content = "Тестовый контент с кириллицей: АБВГД абвгд"
|
|
42
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, encoding="utf-8") as tmp:
|
|
43
|
+
path = tmp.name
|
|
44
|
+
mock_config.output = path
|
|
45
|
+
try:
|
|
46
|
+
service._Service__write_to_output(content)
|
|
47
|
+
_assert_utf8_file_equals(path, content)
|
|
48
|
+
finally:
|
|
49
|
+
if os.path.exists(path):
|
|
50
|
+
os.unlink(path)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_write_to_output_creates_parent_directory(service, mock_config):
|
|
54
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
55
|
+
path = os.path.join(tmp_dir, "nested", "out.txt")
|
|
56
|
+
mock_config.output = path
|
|
57
|
+
service._Service__write_to_output("test content")
|
|
58
|
+
assert os.path.isfile(path)
|
|
59
|
+
_assert_utf8_file_equals(path, "test content")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_rerun_test_run_forwards_config_to_api_client(service, mock_config, api_client):
|
|
63
|
+
mock_config.testrun_id = "3802f329-190c-4617-8bb0-2c3696abeb8f"
|
|
64
|
+
mock_config.configuration_ids = [
|
|
65
|
+
"15dbb164-c1aa-4cbf-830c-8c01ae14f4fb",
|
|
66
|
+
"5236eb3f-7c05-46f9-a609-dc0278896464",
|
|
67
|
+
]
|
|
68
|
+
mock_config.status_codes = ["Failed"]
|
|
69
|
+
mock_config.failure_categories = ["cat-1"]
|
|
70
|
+
mock_config.namespace = "ns"
|
|
71
|
+
mock_config.classname = "MyClass"
|
|
72
|
+
mock_config.auto_test_global_ids = [10, 20]
|
|
73
|
+
mock_config.auto_test_tags = ["smoke"]
|
|
74
|
+
mock_config.exclude_auto_test_tags = ["slow"]
|
|
75
|
+
mock_config.auto_test_name = "test-name"
|
|
76
|
+
mock_config.test_result_ids = [
|
|
77
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
78
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
79
|
+
]
|
|
80
|
+
mock_config.webhook_ids = [
|
|
81
|
+
"cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
service.rerun_test_run()
|
|
85
|
+
|
|
86
|
+
api_client.rerun_test_run.assert_called_once_with(
|
|
87
|
+
"3802f329-190c-4617-8bb0-2c3696abeb8f",
|
|
88
|
+
configuration_ids=[
|
|
89
|
+
"15dbb164-c1aa-4cbf-830c-8c01ae14f4fb",
|
|
90
|
+
"5236eb3f-7c05-46f9-a609-dc0278896464",
|
|
91
|
+
],
|
|
92
|
+
status_codes=["Failed"],
|
|
93
|
+
failure_categories=["cat-1"],
|
|
94
|
+
namespace="ns",
|
|
95
|
+
class_name="MyClass",
|
|
96
|
+
auto_test_global_ids=[10, 20],
|
|
97
|
+
auto_test_tags=["smoke"],
|
|
98
|
+
exclude_auto_test_tags=["slow"],
|
|
99
|
+
auto_test_name="test-name",
|
|
100
|
+
test_result_ids=[
|
|
101
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
102
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
103
|
+
],
|
|
104
|
+
webhook_ids=["cccccccc-cccc-4ccc-8ccc-cccccccccccc"],
|
|
105
|
+
)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class Config:
|
|
7
|
-
url: str
|
|
8
|
-
token: str
|
|
9
|
-
project_id: str
|
|
10
|
-
configuration_id: str
|
|
11
|
-
testrun_id: str
|
|
12
|
-
testrun_name: str
|
|
13
|
-
separator: str
|
|
14
|
-
namespace: str
|
|
15
|
-
classname: str
|
|
16
|
-
results: list[typing.Any]
|
|
17
|
-
is_debug: bool
|
|
18
|
-
output: str
|
|
19
|
-
paths_to_attachments: list[typing.Any]
|
|
20
|
-
disable_cert_validation: bool
|
|
21
|
-
framework: str
|
|
22
|
-
ignore_flaky_failure: bool
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
import validators
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def validate_uuid(ctx, param, value):
|
|
6
|
-
if value is not None:
|
|
7
|
-
if not validators.uuid(value):
|
|
8
|
-
raise click.BadParameter(f'"{value}" uuid of {param.name} (3802f329-190c-4617-8bb0-2c3696abeb8f)')
|
|
9
|
-
|
|
10
|
-
return value
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def validate_url(ctx, param, value):
|
|
14
|
-
if not validators.url(value):
|
|
15
|
-
raise click.BadParameter(f'"{value}" url address of the Test IT instance (https://demo.testit.software)')
|
|
16
|
-
|
|
17
|
-
if value.endswith("/"):
|
|
18
|
-
return value[:-1]
|
|
19
|
-
|
|
20
|
-
return value
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
from click.testing import CliRunner
|
|
2
|
-
import pytest
|
|
3
|
-
|
|
4
|
-
from src.testit_cli.click_commands import execute
|
|
5
|
-
from tests.helper import Helper
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture()
|
|
9
|
-
def runner():
|
|
10
|
-
return CliRunner()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_run_without_command(runner):
|
|
14
|
-
message = ("Options:\n --help Show this message and exit.\n\nCommands:\n autotests_filter "
|
|
15
|
-
"Creating filter by autotests for test frameworks\n results "
|
|
16
|
-
"Uploading the test results\n testrun Working with the test run\n")
|
|
17
|
-
result = runner.invoke(execute, [])
|
|
18
|
-
|
|
19
|
-
assert message in result.output
|
|
20
|
-
assert result.exit_code == 0
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_run_with_another_command(runner):
|
|
24
|
-
another_command = "run"
|
|
25
|
-
message = f"Error: No such command '{another_command}'"
|
|
26
|
-
result = runner.invoke(execute, [another_command])
|
|
27
|
-
|
|
28
|
-
assert message in result.output
|
|
29
|
-
assert result.exit_code == 2
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@pytest.mark.parametrize("command, message", [
|
|
33
|
-
("results", "Options:\n --help Show this message and exit.\n\nCommands:\n import Uploading the first test results\n upload Uploading results from different streams\n"),
|
|
34
|
-
("testrun", "Options:\n --help Show this message and exit.\n\nCommands:\n complete Completing the test run\n create Creating a new test run\n upload_attachments Uploading attachments for the test run\n")])
|
|
35
|
-
def test_run_with_command(runner, command, message):
|
|
36
|
-
result = runner.invoke(execute, [command])
|
|
37
|
-
|
|
38
|
-
assert message in result.output
|
|
39
|
-
assert result.exit_code == 0
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_run_results_with_another_command(runner):
|
|
43
|
-
another_command = "run"
|
|
44
|
-
message = f"Error: No such command '{another_command}'"
|
|
45
|
-
result = runner.invoke(execute, ["results", another_command])
|
|
46
|
-
|
|
47
|
-
assert message in result.output
|
|
48
|
-
assert result.exit_code == 2
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@pytest.mark.parametrize("commands, output", [
|
|
52
|
-
(Helper.get_command_results_import_with_long_arguments_without_url_argument(), Helper.get_output_for_results_import_without_url_argument()),
|
|
53
|
-
(Helper.get_command_results_import_with_short_arguments_without_url_argument(), Helper.get_output_for_results_import_without_url_argument()),
|
|
54
|
-
(Helper.get_command_results_import_with_long_arguments_without_token_argument(), Helper.get_output_for_results_import_without_token_argument()),
|
|
55
|
-
(Helper.get_command_results_import_with_short_arguments_without_token_argument(), Helper.get_output_for_results_import_without_token_argument()),
|
|
56
|
-
(Helper.get_command_results_import_with_long_arguments_without_project_id_argument(), Helper.get_output_for_results_import_without_project_id_argument()),
|
|
57
|
-
(Helper.get_command_results_import_with_short_arguments_without_project_id_argument(), Helper.get_output_for_results_import_without_project_id_argument()),
|
|
58
|
-
(Helper.get_command_results_import_with_long_arguments_without_configuration_id_argument(), Helper.get_output_for_results_import_without_configuration_id_argument()),
|
|
59
|
-
(Helper.get_command_results_import_with_short_arguments_without_configuration_id_argument(), Helper.get_output_for_results_import_without_configuration_id_argument()),
|
|
60
|
-
(Helper.get_command_results_import_with_long_arguments_without_results_argument(), Helper.get_output_for_results_import_without_results_argument()),
|
|
61
|
-
(Helper.get_command_results_import_with_short_arguments_without_results_argument(), Helper.get_output_for_results_import_without_results_argument()),
|
|
62
|
-
(Helper.get_command_results_upload_with_long_arguments_without_url_argument(), Helper.get_output_for_results_upload_without_url_argument()),
|
|
63
|
-
(Helper.get_command_results_upload_with_short_arguments_without_url_argument(), Helper.get_output_for_results_upload_without_url_argument()),
|
|
64
|
-
(Helper.get_command_results_upload_with_long_arguments_without_token_argument(), Helper.get_output_for_results_upload_without_token_argument()),
|
|
65
|
-
(Helper.get_command_results_upload_with_short_arguments_without_token_argument(), Helper.get_output_for_results_upload_without_token_argument()),
|
|
66
|
-
(Helper.get_command_results_upload_with_long_arguments_without_testrun_id_argument(), Helper.get_output_for_results_upload_without_testrun_id_argument()),
|
|
67
|
-
(Helper.get_command_results_upload_with_short_arguments_without_testrun_id_argument(), Helper.get_output_for_results_upload_without_testrun_id_argument()),
|
|
68
|
-
(Helper.get_command_results_upload_with_long_arguments_without_configuration_id_argument(), Helper.get_output_for_results_upload_without_configuration_id_argument()),
|
|
69
|
-
(Helper.get_command_results_upload_with_short_arguments_without_configuration_id_argument(), Helper.get_output_for_results_upload_without_configuration_id_argument()),
|
|
70
|
-
(Helper.get_command_results_upload_with_long_arguments_without_results_argument(), Helper.get_output_for_results_upload_without_results_argument()),
|
|
71
|
-
(Helper.get_command_results_upload_with_short_arguments_without_results_argument(), Helper.get_output_for_results_upload_without_results_argument()),
|
|
72
|
-
(Helper.get_command_testrun_create_with_long_arguments_without_url_argument(), Helper.get_output_for_testrun_create_without_url_argument()),
|
|
73
|
-
(Helper.get_command_testrun_create_with_short_arguments_without_url_argument(), Helper.get_output_for_testrun_create_without_url_argument()),
|
|
74
|
-
(Helper.get_command_testrun_create_with_long_arguments_without_token_argument(), Helper.get_output_for_testrun_create_without_token_argument()),
|
|
75
|
-
(Helper.get_command_testrun_create_with_short_arguments_without_token_argument(), Helper.get_output_for_testrun_create_without_token_argument()),
|
|
76
|
-
(Helper.get_command_testrun_create_with_long_arguments_without_project_id_argument(), Helper.get_output_for_testrun_create_without_project_id_argument()),
|
|
77
|
-
(Helper.get_command_testrun_create_with_short_arguments_without_project_id_argument(), Helper.get_output_for_testrun_create_without_project_id_argument()),
|
|
78
|
-
(Helper.get_command_testrun_create_with_long_arguments_without_output_argument(), Helper.get_output_for_testrun_create_without_output_argument()),
|
|
79
|
-
(Helper.get_command_testrun_create_with_short_arguments_without_output_argument(), Helper.get_output_for_testrun_create_without_output_argument()),
|
|
80
|
-
(Helper.get_command_testrun_complete_with_long_arguments_without_url_argument(), Helper.get_output_for_testrun_complete_without_url_argument()),
|
|
81
|
-
(Helper.get_command_testrun_complete_with_short_arguments_without_url_argument(), Helper.get_output_for_testrun_complete_without_url_argument()),
|
|
82
|
-
(Helper.get_command_testrun_complete_with_long_arguments_without_token_argument(), Helper.get_output_for_testrun_complete_without_token_argument()),
|
|
83
|
-
(Helper.get_command_testrun_complete_with_short_arguments_without_token_argument(), Helper.get_output_for_testrun_complete_without_token_argument()),
|
|
84
|
-
(Helper.get_command_testrun_complete_with_long_arguments_without_testrun_id_argument(), Helper.get_output_for_testrun_complete_without_testrun_id_argument()),
|
|
85
|
-
(Helper.get_command_testrun_complete_with_short_arguments_without_testrun_id_argument(), Helper.get_output_for_testrun_complete_without_testrun_id_argument()),
|
|
86
|
-
(Helper.get_command_testrun_upload_attachments_with_long_arguments_without_url_argument(), Helper.get_output_for_testrun_upload_attachments_without_url_argument()),
|
|
87
|
-
(Helper.get_command_testrun_upload_attachments_with_short_arguments_without_url_argument(), Helper.get_output_for_testrun_upload_attachments_without_url_argument()),
|
|
88
|
-
(Helper.get_command_testrun_upload_attachments_with_long_arguments_without_token_argument(), Helper.get_output_for_testrun_upload_attachments_without_token_argument()),
|
|
89
|
-
(Helper.get_command_testrun_upload_attachments_with_short_arguments_without_token_argument(), Helper.get_output_for_testrun_upload_attachments_without_token_argument()),
|
|
90
|
-
(Helper.get_command_testrun_upload_attachments_with_long_arguments_without_testrun_id_argument(), Helper.get_output_for_testrun_upload_attachments_without_testrun_id_argument()),
|
|
91
|
-
(Helper.get_command_testrun_upload_attachments_with_short_arguments_without_testrun_id_argument(), Helper.get_output_for_testrun_upload_attachments_without_testrun_id_argument())])
|
|
92
|
-
def test_run_results_with_command_without_arguments(runner, commands, output):
|
|
93
|
-
result = runner.invoke(execute, commands)
|
|
94
|
-
|
|
95
|
-
assert output in result.output
|
|
96
|
-
assert result.exit_code == 2
|
|
97
|
-
|
|
98
|
-
# TODO: inifinite loading, fix
|
|
99
|
-
|
|
100
|
-
# @pytest.mark.parametrize("commands_with_args", [
|
|
101
|
-
# Helper.get_command_results_import_with_long_arguments(),
|
|
102
|
-
# Helper.get_command_results_upload_with_long_arguments(),
|
|
103
|
-
# Helper.get_command_results_import_with_short_arguments(),
|
|
104
|
-
# Helper.get_command_results_upload_with_short_arguments(),
|
|
105
|
-
# Helper.get_command_results_import_with_all_long_arguments(),
|
|
106
|
-
# Helper.get_command_results_upload_with_all_long_arguments(),
|
|
107
|
-
# Helper.get_command_results_import_with_all_short_arguments(),
|
|
108
|
-
# Helper.get_command_results_upload_with_all_short_arguments(),
|
|
109
|
-
# Helper.get_command_testrun_complete_with_long_arguments(),
|
|
110
|
-
# Helper.get_command_testrun_create_with_long_arguments(),
|
|
111
|
-
# Helper.get_command_testrun_upload_attachments_with_long_arguments(),
|
|
112
|
-
# Helper.get_command_testrun_complete_with_short_arguments(),
|
|
113
|
-
# Helper.get_command_testrun_create_with_short_arguments(),
|
|
114
|
-
# Helper.get_command_testrun_upload_attachments_with_short_arguments(),
|
|
115
|
-
# Helper.get_command_testrun_complete_with_all_long_arguments(),
|
|
116
|
-
# Helper.get_command_testrun_create_with_all_long_arguments(),
|
|
117
|
-
# Helper.get_command_testrun_upload_attachments_with_all_long_arguments(),
|
|
118
|
-
# Helper.get_command_testrun_complete_with_all_short_arguments(),
|
|
119
|
-
# Helper.get_command_testrun_create_with_all_short_arguments(),
|
|
120
|
-
# Helper.get_command_testrun_upload_attachments_with_all_short_arguments()])
|
|
121
|
-
# def test_run_with_commands_and_arguments(runner, commands_with_args):
|
|
122
|
-
# result = runner.invoke(execute, commands_with_args)
|
|
123
|
-
|
|
124
|
-
# assert result.exit_code == 1
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@pytest.mark.parametrize("commands_with_another_arg", [
|
|
128
|
-
Helper.get_command_results_upload_with_another_argument(),
|
|
129
|
-
Helper.get_command_results_import_with_another_argument(),
|
|
130
|
-
Helper.get_command_testrun_complete_with_another_argument(),
|
|
131
|
-
Helper.get_command_testrun_create_with_another_argument(),
|
|
132
|
-
Helper.get_command_testrun_upload_attachments_with_another_argument()])
|
|
133
|
-
def test_run_with_command_and_another_argument(runner, commands_with_another_arg):
|
|
134
|
-
result = runner.invoke(execute, commands_with_another_arg)
|
|
135
|
-
|
|
136
|
-
assert result.exit_code == 2
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import tempfile
|
|
3
|
-
import pytest
|
|
4
|
-
from unittest.mock import Mock
|
|
5
|
-
|
|
6
|
-
from src.testit_cli.service import Service
|
|
7
|
-
from src.testit_cli.models.config import Config
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def mock_config():
|
|
12
|
-
"""Fixture for creating mock config object"""
|
|
13
|
-
config = Mock(spec=Config)
|
|
14
|
-
return config
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@pytest.fixture
|
|
18
|
-
def service(mock_config):
|
|
19
|
-
"""Fixture for creating Service instance with mock dependencies"""
|
|
20
|
-
api_client = Mock()
|
|
21
|
-
parser = Mock()
|
|
22
|
-
importer = Mock()
|
|
23
|
-
autotests_filter = Mock()
|
|
24
|
-
|
|
25
|
-
return Service(
|
|
26
|
-
config=mock_config,
|
|
27
|
-
api_client=api_client,
|
|
28
|
-
parser=parser,
|
|
29
|
-
importer=importer,
|
|
30
|
-
autotests_filter=autotests_filter
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_write_to_output_uses_utf8_encoding(service, mock_config):
|
|
35
|
-
"""Test verifies that file is written using UTF-8 encoding"""
|
|
36
|
-
# Arrange
|
|
37
|
-
cyrillic_content = "Тестовый контент с кириллицей: АБВГД абвгд"
|
|
38
|
-
|
|
39
|
-
with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as temp_file:
|
|
40
|
-
temp_output_path = temp_file.name
|
|
41
|
-
|
|
42
|
-
mock_config.output = temp_output_path
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Act - call private method using name mangling
|
|
46
|
-
service._Service__write_to_output(cyrillic_content)
|
|
47
|
-
|
|
48
|
-
# Assert - verify file is written in UTF-8
|
|
49
|
-
with open(temp_output_path, 'r', encoding='utf-8') as file:
|
|
50
|
-
written_content = file.read()
|
|
51
|
-
assert written_content == cyrillic_content
|
|
52
|
-
|
|
53
|
-
# Additional check - read as bytes and verify UTF-8 BOM is absent
|
|
54
|
-
with open(temp_output_path, 'rb') as file:
|
|
55
|
-
raw_content = file.read()
|
|
56
|
-
# Verify content can be decoded as UTF-8
|
|
57
|
-
decoded_content = raw_content.decode('utf-8')
|
|
58
|
-
assert decoded_content == cyrillic_content
|
|
59
|
-
|
|
60
|
-
finally:
|
|
61
|
-
# Cleanup
|
|
62
|
-
if os.path.exists(temp_output_path):
|
|
63
|
-
os.unlink(temp_output_path)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def test_write_to_output_creates_directory(service, mock_config):
|
|
67
|
-
"""Test verifies that directory is created if it doesn't exist"""
|
|
68
|
-
# Arrange
|
|
69
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
70
|
-
output_path = os.path.join(temp_dir, "subdir", "output.txt")
|
|
71
|
-
mock_config.output = output_path
|
|
72
|
-
|
|
73
|
-
# Act
|
|
74
|
-
service._Service__write_to_output("test content")
|
|
75
|
-
|
|
76
|
-
# Assert
|
|
77
|
-
assert os.path.exists(output_path)
|
|
78
|
-
with open(output_path, 'r', encoding='utf-8') as file:
|
|
79
|
-
assert file.read() == "test content"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|