lucius-mcp 0.2.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- lucius_mcp-0.4.0.dist-info/METADATA +124 -0
- {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.4.0.dist-info}/RECORD +53 -25
- src/client/__init__.py +16 -0
- src/client/client.py +559 -14
- src/client/exceptions.py +23 -0
- src/client/generated/README.md +29 -0
- src/client/generated/__init__.py +6 -0
- src/client/generated/api/__init__.py +3 -0
- src/client/generated/api/integration_controller_api.py +5285 -0
- src/client/generated/api/test_layer_controller_api.py +1746 -0
- src/client/generated/api/test_layer_schema_controller_api.py +1415 -0
- src/client/generated/docs/IntegrationControllerApi.md +1224 -0
- src/client/generated/docs/TestLayerControllerApi.md +407 -0
- src/client/generated/docs/TestLayerSchemaControllerApi.md +350 -0
- src/client/overridden/test_case_custom_fields_v2.py +254 -0
- src/services/__init__.py +16 -0
- src/services/custom_field_value_service.py +301 -0
- src/services/integration_service.py +205 -0
- src/services/launch_service.py +306 -0
- src/services/search_service.py +1 -1
- src/services/shared_step_service.py +34 -17
- src/services/test_case_service.py +769 -117
- src/services/test_layer_service.py +427 -0
- src/tools/__init__.py +53 -0
- src/tools/create_custom_field_value.py +38 -0
- src/tools/create_test_case.py +68 -19
- src/tools/create_test_layer.py +33 -0
- src/tools/create_test_layer_schema.py +39 -0
- src/tools/delete_custom_field_value.py +57 -0
- src/tools/delete_test_case.py +5 -4
- src/tools/delete_test_layer.py +44 -0
- src/tools/delete_test_layer_schema.py +44 -0
- src/tools/get_custom_fields.py +2 -1
- src/tools/get_test_case_custom_fields.py +34 -0
- src/tools/launches.py +198 -0
- src/tools/link_shared_step.py +18 -12
- src/tools/list_custom_field_values.py +72 -0
- src/tools/list_integrations.py +77 -0
- src/tools/list_test_layer_schemas.py +43 -0
- src/tools/list_test_layers.py +38 -0
- src/tools/search.py +9 -6
- src/tools/shared_steps.py +23 -8
- src/tools/test_layers.py +21 -0
- src/tools/unlink_shared_step.py +19 -5
- src/tools/update_custom_field_value.py +55 -0
- src/tools/update_test_case.py +113 -23
- src/tools/update_test_layer.py +44 -0
- src/tools/update_test_layer_schema.py +51 -0
- src/utils/__init__.py +4 -0
- src/utils/links.py +13 -0
- lucius_mcp-0.2.2.dist-info/METADATA +0 -290
- {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.4.0.dist-info}/WHEEL +0 -0
- {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.4.0.dist-info}/entry_points.txt +0 -0
- {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.4.0.dist-info}/licenses/LICENSE +0 -0
src/client/client.py
CHANGED
|
@@ -10,7 +10,7 @@ from collections.abc import Awaitable
|
|
|
10
10
|
from typing import Literal, TypeVar, cast, overload
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
|
-
from pydantic import SecretStr
|
|
13
|
+
from pydantic import Field, SecretStr, ValidationError
|
|
14
14
|
|
|
15
15
|
from src.client.exceptions import TestCaseNotFoundError
|
|
16
16
|
from src.utils.config import settings
|
|
@@ -26,27 +26,45 @@ from .exceptions import (
|
|
|
26
26
|
from .generated.api.custom_field_controller_api import CustomFieldControllerApi
|
|
27
27
|
from .generated.api.custom_field_project_controller_api import CustomFieldProjectControllerApi
|
|
28
28
|
from .generated.api.custom_field_project_controller_v2_api import CustomFieldProjectControllerV2Api
|
|
29
|
+
from .generated.api.custom_field_value_controller_api import CustomFieldValueControllerApi
|
|
29
30
|
from .generated.api.custom_field_value_project_controller_api import CustomFieldValueProjectControllerApi
|
|
31
|
+
from .generated.api.integration_controller_api import IntegrationControllerApi
|
|
32
|
+
from .generated.api.launch_controller_api import LaunchControllerApi
|
|
33
|
+
from .generated.api.launch_search_controller_api import LaunchSearchControllerApi
|
|
30
34
|
from .generated.api.shared_step_attachment_controller_api import SharedStepAttachmentControllerApi
|
|
31
35
|
from .generated.api.shared_step_controller_api import SharedStepControllerApi
|
|
32
36
|
from .generated.api.shared_step_scenario_controller_api import SharedStepScenarioControllerApi
|
|
33
37
|
from .generated.api.test_case_attachment_controller_api import TestCaseAttachmentControllerApi
|
|
34
38
|
from .generated.api.test_case_controller_api import TestCaseControllerApi
|
|
35
|
-
from .generated.api.test_case_custom_field_controller_api import TestCaseCustomFieldControllerApi
|
|
36
39
|
from .generated.api.test_case_overview_controller_api import TestCaseOverviewControllerApi
|
|
37
40
|
from .generated.api.test_case_scenario_controller_api import TestCaseScenarioControllerApi
|
|
38
41
|
from .generated.api.test_case_search_controller_api import TestCaseSearchControllerApi
|
|
42
|
+
from .generated.api.test_layer_controller_api import TestLayerControllerApi
|
|
43
|
+
from .generated.api.test_layer_schema_controller_api import TestLayerSchemaControllerApi
|
|
39
44
|
from .generated.api_client import ApiClient
|
|
40
45
|
from .generated.configuration import Configuration
|
|
41
46
|
from .generated.exceptions import ApiException
|
|
47
|
+
from .generated.models.aql_validate_response_dto import AqlValidateResponseDto
|
|
42
48
|
from .generated.models.attachment_step_dto import AttachmentStepDto
|
|
43
49
|
from .generated.models.body_step_dto import BodyStepDto
|
|
44
50
|
from .generated.models.custom_field_project_with_values_dto import CustomFieldProjectWithValuesDto
|
|
51
|
+
from .generated.models.custom_field_value_project_create_dto import CustomFieldValueProjectCreateDto
|
|
52
|
+
from .generated.models.custom_field_value_project_patch_dto import CustomFieldValueProjectPatchDto
|
|
45
53
|
from .generated.models.custom_field_value_with_cf_dto import CustomFieldValueWithCfDto
|
|
54
|
+
from .generated.models.custom_field_with_values_dto import CustomFieldWithValuesDto
|
|
55
|
+
from .generated.models.find_all29200_response import FindAll29200Response
|
|
56
|
+
from .generated.models.integration_dto import IntegrationDto
|
|
57
|
+
from .generated.models.issue_dto import IssueDto
|
|
58
|
+
from .generated.models.launch_create_dto import LaunchCreateDto
|
|
59
|
+
from .generated.models.launch_dto import LaunchDto
|
|
60
|
+
from .generated.models.page_custom_field_value_with_tc_count_dto import PageCustomFieldValueWithTcCountDto
|
|
61
|
+
from .generated.models.page_launch_dto import PageLaunchDto
|
|
62
|
+
from .generated.models.page_launch_preview_dto import PageLaunchPreviewDto
|
|
46
63
|
from .generated.models.page_shared_step_dto import PageSharedStepDto
|
|
47
64
|
from .generated.models.page_test_case_dto import PageTestCaseDto
|
|
48
65
|
from .generated.models.scenario_step_create_dto import ScenarioStepCreateDto
|
|
49
66
|
from .generated.models.scenario_step_created_response_dto import ScenarioStepCreatedResponseDto
|
|
67
|
+
from .generated.models.scenario_step_patch_dto import ScenarioStepPatchDto
|
|
50
68
|
from .generated.models.shared_step_attachment_row_dto import SharedStepAttachmentRowDto
|
|
51
69
|
from .generated.models.shared_step_create_dto import SharedStepCreateDto
|
|
52
70
|
from .generated.models.shared_step_dto import SharedStepDto
|
|
@@ -62,13 +80,18 @@ from .generated.models.test_case_row_dto import TestCaseRowDto
|
|
|
62
80
|
from .generated.models.test_case_scenario_dto import TestCaseScenarioDto
|
|
63
81
|
from .generated.models.test_case_scenario_v2_dto import TestCaseScenarioV2Dto
|
|
64
82
|
from .generated.models.test_case_tree_selection_dto import TestCaseTreeSelectionDto
|
|
83
|
+
from .overridden.test_case_custom_fields_v2 import TestCaseCustomFieldV2ControllerApi
|
|
65
84
|
|
|
66
85
|
|
|
67
86
|
# Subclasses to add missing fields to generated models
|
|
68
87
|
class TestCaseDtoWithCF(TestCaseDto):
|
|
69
|
-
"""Subclass to support custom_fields access."""
|
|
88
|
+
"""Subclass to support custom_fields and issues access."""
|
|
70
89
|
|
|
71
90
|
custom_fields: list[CustomFieldValueWithCfDto] | None = None
|
|
91
|
+
issues: list[IssueDto] | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
TestCaseDtoWithCF.model_rebuild()
|
|
72
95
|
|
|
73
96
|
|
|
74
97
|
class BodyStepDtoWithSteps(BodyStepDto):
|
|
@@ -78,6 +101,14 @@ class BodyStepDtoWithSteps(BodyStepDto):
|
|
|
78
101
|
id: int | None = None
|
|
79
102
|
|
|
80
103
|
|
|
104
|
+
class StepWithExpected(BodyStepDto):
|
|
105
|
+
"""Subclass to support expected results and nested steps."""
|
|
106
|
+
|
|
107
|
+
expected_result: str | None = Field(default=None, alias="expectedResult")
|
|
108
|
+
steps: list[SharedStepScenarioDtoStepsInner] | None = None
|
|
109
|
+
id: int | None = None
|
|
110
|
+
|
|
111
|
+
|
|
81
112
|
class AttachmentStepDtoWithName(AttachmentStepDto):
|
|
82
113
|
"""Subclass to support name attribute and id."""
|
|
83
114
|
|
|
@@ -104,11 +135,17 @@ type ApiType = (
|
|
|
104
135
|
| SharedStepScenarioControllerApi
|
|
105
136
|
| TestCaseOverviewControllerApi
|
|
106
137
|
| TestCaseSearchControllerApi
|
|
107
|
-
|
|
|
138
|
+
| TestCaseCustomFieldV2ControllerApi
|
|
108
139
|
| CustomFieldControllerApi
|
|
109
140
|
| CustomFieldProjectControllerApi
|
|
110
141
|
| CustomFieldProjectControllerV2Api
|
|
142
|
+
| CustomFieldValueControllerApi
|
|
111
143
|
| CustomFieldValueProjectControllerApi
|
|
144
|
+
| TestLayerControllerApi
|
|
145
|
+
| TestLayerSchemaControllerApi
|
|
146
|
+
| LaunchControllerApi
|
|
147
|
+
| LaunchSearchControllerApi
|
|
148
|
+
| IntegrationControllerApi
|
|
112
149
|
)
|
|
113
150
|
|
|
114
151
|
type NormalizedScenarioDict = dict[str, object]
|
|
@@ -123,16 +160,24 @@ __all__ = [
|
|
|
123
160
|
"AttachmentStepDtoWithName",
|
|
124
161
|
"BodyStepDtoWithSteps",
|
|
125
162
|
"CustomFieldProjectWithValuesDto",
|
|
163
|
+
"CustomFieldWithValuesDto",
|
|
164
|
+
"FindAll29200Response",
|
|
165
|
+
"LaunchCreateDto",
|
|
166
|
+
"LaunchDto",
|
|
167
|
+
"PageLaunchDto",
|
|
168
|
+
"PageLaunchPreviewDto",
|
|
126
169
|
"PageSharedStepDto",
|
|
127
170
|
"PageTestCaseDto",
|
|
128
171
|
"ScenarioStepCreateDto",
|
|
129
172
|
"ScenarioStepCreatedResponseDto",
|
|
173
|
+
"ScenarioStepPatchDto",
|
|
130
174
|
"SharedStepAttachmentRowDto",
|
|
131
175
|
"SharedStepCreateDto",
|
|
132
176
|
"SharedStepDto",
|
|
133
177
|
"SharedStepPatchDto",
|
|
134
178
|
"SharedStepScenarioDtoStepsInner",
|
|
135
179
|
"SharedStepStepDtoWithId",
|
|
180
|
+
"StepWithExpected",
|
|
136
181
|
"TestCaseAttachmentRowDto",
|
|
137
182
|
"TestCaseCreateV2Dto",
|
|
138
183
|
"TestCaseDto",
|
|
@@ -204,11 +249,17 @@ class AllureClient:
|
|
|
204
249
|
self._shared_step_scenario_api: SharedStepScenarioControllerApi | None = None
|
|
205
250
|
self._overview_api: TestCaseOverviewControllerApi
|
|
206
251
|
self._search_api: TestCaseSearchControllerApi | None = None
|
|
207
|
-
self._test_case_custom_field_api:
|
|
252
|
+
self._test_case_custom_field_api: TestCaseCustomFieldV2ControllerApi | None = None
|
|
208
253
|
self._custom_field_api: CustomFieldControllerApi | None = None
|
|
209
254
|
self._custom_field_project_api: CustomFieldProjectControllerApi | None = None
|
|
210
255
|
self._custom_field_project_v2_api: CustomFieldProjectControllerV2Api | None = None
|
|
256
|
+
self._custom_field_value_api: CustomFieldValueControllerApi | None = None
|
|
211
257
|
self._custom_field_value_project_api: CustomFieldValueProjectControllerApi | None = None
|
|
258
|
+
self._test_layer_api: TestLayerControllerApi | None = None
|
|
259
|
+
self._test_layer_schema_api: TestLayerSchemaControllerApi | None = None
|
|
260
|
+
self._launch_api: LaunchControllerApi | None = None
|
|
261
|
+
self._launch_search_api: LaunchSearchControllerApi | None = None
|
|
262
|
+
self._integration_api: IntegrationControllerApi | None = None
|
|
212
263
|
self._is_entered = False
|
|
213
264
|
|
|
214
265
|
@classmethod
|
|
@@ -239,7 +290,7 @@ class AllureClient:
|
|
|
239
290
|
if not isinstance(settings.ALLURE_PROJECT_ID, int) or settings.ALLURE_PROJECT_ID <= 0:
|
|
240
291
|
raise ValueError("ALLURE_PROJECT_ID must be a positive integer")
|
|
241
292
|
|
|
242
|
-
if project:
|
|
293
|
+
if project is not None:
|
|
243
294
|
p = project
|
|
244
295
|
else:
|
|
245
296
|
p = settings.ALLURE_PROJECT_ID
|
|
@@ -345,11 +396,17 @@ class AllureClient:
|
|
|
345
396
|
self._shared_step_scenario_api = SharedStepScenarioControllerApi(self._api_client)
|
|
346
397
|
self._overview_api = TestCaseOverviewControllerApi(self._api_client)
|
|
347
398
|
self._search_api = TestCaseSearchControllerApi(self._api_client)
|
|
348
|
-
self._test_case_custom_field_api =
|
|
399
|
+
self._test_case_custom_field_api = TestCaseCustomFieldV2ControllerApi(self._api_client)
|
|
349
400
|
self._custom_field_api = CustomFieldControllerApi(self._api_client)
|
|
350
401
|
self._custom_field_project_api = CustomFieldProjectControllerApi(self._api_client)
|
|
351
402
|
self._custom_field_project_v2_api = CustomFieldProjectControllerV2Api(self._api_client)
|
|
403
|
+
self._custom_field_value_api = CustomFieldValueControllerApi(self._api_client)
|
|
352
404
|
self._custom_field_value_project_api = CustomFieldValueProjectControllerApi(self._api_client)
|
|
405
|
+
self._test_layer_api = TestLayerControllerApi(self._api_client)
|
|
406
|
+
self._test_layer_schema_api = TestLayerSchemaControllerApi(self._api_client)
|
|
407
|
+
self._launch_api = LaunchControllerApi(self._api_client)
|
|
408
|
+
self._launch_search_api = LaunchSearchControllerApi(self._api_client)
|
|
409
|
+
self._integration_api = IntegrationControllerApi(self._api_client)
|
|
353
410
|
|
|
354
411
|
@property
|
|
355
412
|
def api_client(self) -> ApiClient:
|
|
@@ -362,6 +419,21 @@ class AllureClient:
|
|
|
362
419
|
raise RuntimeError("AllureClient must be used as an async context manager")
|
|
363
420
|
return self._api_client
|
|
364
421
|
|
|
422
|
+
async def get_integrations(self) -> list[IntegrationDto]:
|
|
423
|
+
"""Fetch all integrations."""
|
|
424
|
+
# Ensure we have a valid client
|
|
425
|
+
if self._integration_api is None:
|
|
426
|
+
raise RuntimeError("AllureClient must be used as an async context manager")
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
# Fetch first page with reasonable size
|
|
430
|
+
page = await self._integration_api.get_integrations(page=0, size=100)
|
|
431
|
+
return page.content or []
|
|
432
|
+
except Exception:
|
|
433
|
+
# Log warning or re-raise depending on strictness.
|
|
434
|
+
# For now return empty list to act as fallback.
|
|
435
|
+
return []
|
|
436
|
+
|
|
365
437
|
async def __aenter__(self) -> AllureClient:
|
|
366
438
|
"""Initialize the client session within an async context.
|
|
367
439
|
|
|
@@ -466,7 +538,7 @@ class AllureClient:
|
|
|
466
538
|
@overload
|
|
467
539
|
async def _get_api(
|
|
468
540
|
self, attr_name: Literal["_test_case_custom_field_api"], *, error_name: str | None = None
|
|
469
|
-
) ->
|
|
541
|
+
) -> TestCaseCustomFieldV2ControllerApi: ...
|
|
470
542
|
|
|
471
543
|
@overload
|
|
472
544
|
async def _get_api(
|
|
@@ -483,11 +555,26 @@ class AllureClient:
|
|
|
483
555
|
self, attr_name: Literal["_custom_field_project_v2_api"], *, error_name: str | None = None
|
|
484
556
|
) -> CustomFieldProjectControllerV2Api: ...
|
|
485
557
|
|
|
558
|
+
@overload
|
|
559
|
+
async def _get_api(
|
|
560
|
+
self, attr_name: Literal["_custom_field_value_api"], *, error_name: str | None = None
|
|
561
|
+
) -> CustomFieldValueControllerApi: ...
|
|
562
|
+
|
|
486
563
|
@overload
|
|
487
564
|
async def _get_api(
|
|
488
565
|
self, attr_name: Literal["_custom_field_value_project_api"], *, error_name: str | None = None
|
|
489
566
|
) -> CustomFieldValueProjectControllerApi: ...
|
|
490
567
|
|
|
568
|
+
@overload
|
|
569
|
+
async def _get_api(
|
|
570
|
+
self, attr_name: Literal["_launch_api"], *, error_name: str | None = None
|
|
571
|
+
) -> LaunchControllerApi: ...
|
|
572
|
+
|
|
573
|
+
@overload
|
|
574
|
+
async def _get_api(
|
|
575
|
+
self, attr_name: Literal["_launch_search_api"], *, error_name: str | None = None
|
|
576
|
+
) -> LaunchSearchControllerApi: ...
|
|
577
|
+
|
|
491
578
|
async def _get_api(self, attr_name: str, *, error_name: str | None = None) -> ApiType:
|
|
492
579
|
self._require_entered()
|
|
493
580
|
await self._ensure_valid_token()
|
|
@@ -693,6 +780,210 @@ class AllureClient:
|
|
|
693
780
|
)
|
|
694
781
|
)
|
|
695
782
|
|
|
783
|
+
# ==========================================
|
|
784
|
+
# Launch operations
|
|
785
|
+
# ==========================================
|
|
786
|
+
|
|
787
|
+
async def create_launch(self, data: LaunchCreateDto) -> LaunchDto:
|
|
788
|
+
"""Create a new launch in the specified project.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
data: Launch definition (name, project_id, etc.).
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
The created launch.
|
|
795
|
+
|
|
796
|
+
Raises:
|
|
797
|
+
AllureNotFoundError: If project doesn't exist.
|
|
798
|
+
AllureValidationError: If input data fails validation.
|
|
799
|
+
AllureAuthError: If unauthorized.
|
|
800
|
+
AllureAPIError: If the server returns an error.
|
|
801
|
+
"""
|
|
802
|
+
api = await self._get_api("_launch_api")
|
|
803
|
+
|
|
804
|
+
if hasattr(data, "project_id") and not data.project_id:
|
|
805
|
+
data.project_id = self._project
|
|
806
|
+
|
|
807
|
+
return await self._call_api(api.create31(launch_create_dto=data, _request_timeout=self._timeout))
|
|
808
|
+
|
|
809
|
+
async def list_launches(
|
|
810
|
+
self,
|
|
811
|
+
project_id: int,
|
|
812
|
+
page: int = 0,
|
|
813
|
+
size: int = 20,
|
|
814
|
+
search: str | None = None,
|
|
815
|
+
filter_id: int | None = None,
|
|
816
|
+
sort: list[str] | None = None,
|
|
817
|
+
) -> FindAll29200Response:
|
|
818
|
+
"""List launches for a project.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
project_id: Target project ID.
|
|
822
|
+
page: Zero-based page index.
|
|
823
|
+
size: Page size.
|
|
824
|
+
search: Optional name search.
|
|
825
|
+
filter_id: Optional filter ID.
|
|
826
|
+
sort: Optional sort criteria.
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
Paginated launches or launch previews.
|
|
830
|
+
|
|
831
|
+
Raises:
|
|
832
|
+
AllureNotFoundError: If project doesn't exist.
|
|
833
|
+
AllureValidationError: If input data fails validation.
|
|
834
|
+
AllureAuthError: If unauthorized.
|
|
835
|
+
AllureAPIError: If the server returns an error.
|
|
836
|
+
"""
|
|
837
|
+
api = await self._get_api("_launch_api", error_name="launch APIs")
|
|
838
|
+
|
|
839
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
840
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
841
|
+
if not isinstance(page, int) or page < 0:
|
|
842
|
+
raise AllureValidationError("Page must be a non-negative integer")
|
|
843
|
+
if not isinstance(size, int) or size <= 0 or size > 100:
|
|
844
|
+
raise AllureValidationError("Size must be between 1 and 100")
|
|
845
|
+
|
|
846
|
+
try:
|
|
847
|
+
return await self._call_api(
|
|
848
|
+
api.find_all29(
|
|
849
|
+
project_id=project_id,
|
|
850
|
+
search=search,
|
|
851
|
+
filter_id=filter_id,
|
|
852
|
+
page=page,
|
|
853
|
+
size=size,
|
|
854
|
+
sort=sort,
|
|
855
|
+
_request_timeout=self._timeout,
|
|
856
|
+
)
|
|
857
|
+
)
|
|
858
|
+
except ValueError:
|
|
859
|
+
response = await self._call_api_raw(
|
|
860
|
+
api.find_all29_without_preload_content(
|
|
861
|
+
project_id=project_id,
|
|
862
|
+
search=search,
|
|
863
|
+
filter_id=filter_id,
|
|
864
|
+
page=page,
|
|
865
|
+
size=size,
|
|
866
|
+
sort=sort,
|
|
867
|
+
_request_timeout=self._timeout,
|
|
868
|
+
)
|
|
869
|
+
)
|
|
870
|
+
data = self._extract_response_data(response)
|
|
871
|
+
try:
|
|
872
|
+
page_data = PageLaunchDto.from_dict(data)
|
|
873
|
+
if page_data is None:
|
|
874
|
+
raise AllureValidationError("Unexpected launch list response from API")
|
|
875
|
+
return FindAll29200Response(page_data)
|
|
876
|
+
except ValidationError as e:
|
|
877
|
+
preview_data = PageLaunchPreviewDto.from_dict(data)
|
|
878
|
+
if preview_data is None:
|
|
879
|
+
raise AllureValidationError("Unexpected launch list response from API") from e
|
|
880
|
+
return FindAll29200Response(preview_data)
|
|
881
|
+
|
|
882
|
+
async def get_launch(self, launch_id: int) -> LaunchDto:
|
|
883
|
+
"""Retrieve a specific launch by its ID.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
launch_id: The unique ID of the launch.
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
The launch data.
|
|
890
|
+
|
|
891
|
+
Raises:
|
|
892
|
+
AllureNotFoundError: If launch doesn't exist.
|
|
893
|
+
AllureValidationError: If input is invalid.
|
|
894
|
+
AllureAuthError: If unauthorized.
|
|
895
|
+
AllureAPIError: If the server returns an error.
|
|
896
|
+
"""
|
|
897
|
+
api = await self._get_api("_launch_api", error_name="launch APIs")
|
|
898
|
+
|
|
899
|
+
if not isinstance(launch_id, int) or launch_id <= 0:
|
|
900
|
+
raise AllureValidationError("Launch ID must be a positive integer")
|
|
901
|
+
|
|
902
|
+
return await self._call_api(api.find_one23(id=launch_id, _request_timeout=self._timeout))
|
|
903
|
+
|
|
904
|
+
async def search_launches_aql(
|
|
905
|
+
self,
|
|
906
|
+
project_id: int,
|
|
907
|
+
rql: str,
|
|
908
|
+
page: int = 0,
|
|
909
|
+
size: int = 20,
|
|
910
|
+
sort: list[str] | None = None,
|
|
911
|
+
) -> PageLaunchDto:
|
|
912
|
+
"""Search launches using raw AQL (Allure Query Language).
|
|
913
|
+
|
|
914
|
+
Args:
|
|
915
|
+
project_id: Target project ID.
|
|
916
|
+
rql: Raw AQL query string.
|
|
917
|
+
page: Zero-based page index.
|
|
918
|
+
size: Page size (max 100).
|
|
919
|
+
sort: Optional sort criteria (e.g., ["createdDate,DESC"]).
|
|
920
|
+
|
|
921
|
+
Returns:
|
|
922
|
+
Paginated launch results matching the AQL query.
|
|
923
|
+
|
|
924
|
+
Raises:
|
|
925
|
+
AllureValidationError: If AQL syntax is invalid or input fails validation.
|
|
926
|
+
AllureNotFoundError: If project doesn't exist.
|
|
927
|
+
AllureAuthError: If unauthorized.
|
|
928
|
+
AllureAPIError: If the server returns an error.
|
|
929
|
+
"""
|
|
930
|
+
api = await self._get_api("_launch_search_api", error_name="launch search APIs")
|
|
931
|
+
|
|
932
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
933
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
934
|
+
if not isinstance(rql, str) or not rql.strip():
|
|
935
|
+
raise AllureValidationError("AQL query must be a non-empty string")
|
|
936
|
+
if not isinstance(page, int) or page < 0:
|
|
937
|
+
raise AllureValidationError("Page must be a non-negative integer")
|
|
938
|
+
if not isinstance(size, int) or size <= 0 or size > 100:
|
|
939
|
+
raise AllureValidationError("Size must be between 1 and 100")
|
|
940
|
+
|
|
941
|
+
return await self._call_api(
|
|
942
|
+
api.search2(
|
|
943
|
+
project_id=project_id,
|
|
944
|
+
rql=rql,
|
|
945
|
+
page=page,
|
|
946
|
+
size=size,
|
|
947
|
+
sort=sort,
|
|
948
|
+
_request_timeout=self._timeout,
|
|
949
|
+
)
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
async def validate_launch_query(
|
|
953
|
+
self,
|
|
954
|
+
project_id: int,
|
|
955
|
+
rql: str,
|
|
956
|
+
) -> AqlValidateResponseDto:
|
|
957
|
+
"""Validate an AQL query for launches without executing it.
|
|
958
|
+
|
|
959
|
+
Args:
|
|
960
|
+
project_id: Target project ID.
|
|
961
|
+
rql: Raw AQL query string to validate.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
Validation response with validity and count.
|
|
965
|
+
|
|
966
|
+
Raises:
|
|
967
|
+
AllureValidationError: If input fails basic validation.
|
|
968
|
+
AllureNotFoundError: If project doesn't exist.
|
|
969
|
+
AllureAuthError: If unauthorized.
|
|
970
|
+
AllureAPIError: If the server returns an error.
|
|
971
|
+
"""
|
|
972
|
+
api = await self._get_api("_launch_search_api", error_name="launch search APIs")
|
|
973
|
+
|
|
974
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
975
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
976
|
+
if not isinstance(rql, str) or not rql.strip():
|
|
977
|
+
raise AllureValidationError("AQL query must be a non-empty string")
|
|
978
|
+
|
|
979
|
+
return await self._call_api(
|
|
980
|
+
api.validate_query2(
|
|
981
|
+
project_id=project_id,
|
|
982
|
+
rql=rql,
|
|
983
|
+
_request_timeout=self._timeout,
|
|
984
|
+
)
|
|
985
|
+
)
|
|
986
|
+
|
|
696
987
|
async def search_test_cases_aql(
|
|
697
988
|
self,
|
|
698
989
|
project_id: int,
|
|
@@ -867,6 +1158,165 @@ class AllureClient:
|
|
|
867
1158
|
with_expected_result=with_expected_result,
|
|
868
1159
|
)
|
|
869
1160
|
|
|
1161
|
+
async def list_custom_field_values(
|
|
1162
|
+
self,
|
|
1163
|
+
project_id: int,
|
|
1164
|
+
custom_field_id: int,
|
|
1165
|
+
*,
|
|
1166
|
+
query: str | None = None,
|
|
1167
|
+
var_global: bool | None = None,
|
|
1168
|
+
test_case_search: str | None = None,
|
|
1169
|
+
page: int | None = None,
|
|
1170
|
+
size: int | None = None,
|
|
1171
|
+
sort: list[str] | None = None,
|
|
1172
|
+
) -> PageCustomFieldValueWithTcCountDto:
|
|
1173
|
+
"""List custom field values for a project field.
|
|
1174
|
+
|
|
1175
|
+
Args:
|
|
1176
|
+
project_id: Target project ID.
|
|
1177
|
+
custom_field_id: Target custom field ID (project-scoped).
|
|
1178
|
+
query: Optional search query.
|
|
1179
|
+
var_global: Optional global flag filter.
|
|
1180
|
+
test_case_search: Optional test case search filter.
|
|
1181
|
+
page: Zero-based page index.
|
|
1182
|
+
size: Page size.
|
|
1183
|
+
sort: Optional sort criteria.
|
|
1184
|
+
|
|
1185
|
+
Returns:
|
|
1186
|
+
Paginated custom field values with test case counts.
|
|
1187
|
+
|
|
1188
|
+
Raises:
|
|
1189
|
+
AllureNotFoundError: If project or custom field doesn't exist.
|
|
1190
|
+
AllureValidationError: If input data fails validation.
|
|
1191
|
+
AllureAuthError: If unauthorized.
|
|
1192
|
+
AllureAPIError: If the server returns an error.
|
|
1193
|
+
"""
|
|
1194
|
+
api = await self._get_api("_custom_field_value_project_api")
|
|
1195
|
+
|
|
1196
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
1197
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
1198
|
+
if not isinstance(custom_field_id, int) or custom_field_id == 0:
|
|
1199
|
+
raise AllureValidationError("Custom Field ID must be a non-zero integer")
|
|
1200
|
+
if page is not None and (not isinstance(page, int) or page < 0):
|
|
1201
|
+
raise AllureValidationError("Page must be a non-negative integer")
|
|
1202
|
+
if size is not None and (not isinstance(size, int) or size <= 0 or size > 1000):
|
|
1203
|
+
raise AllureValidationError("Size must be between 1 and 1000")
|
|
1204
|
+
|
|
1205
|
+
return await self._call_api(
|
|
1206
|
+
api.find_all22(
|
|
1207
|
+
project_id=project_id,
|
|
1208
|
+
custom_field_id=custom_field_id,
|
|
1209
|
+
query=query,
|
|
1210
|
+
var_global=var_global,
|
|
1211
|
+
test_case_search=test_case_search,
|
|
1212
|
+
page=page,
|
|
1213
|
+
size=size,
|
|
1214
|
+
sort=sort,
|
|
1215
|
+
_request_timeout=self._timeout,
|
|
1216
|
+
)
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
async def create_custom_field_value(
|
|
1220
|
+
self, project_id: int, data: CustomFieldValueProjectCreateDto
|
|
1221
|
+
) -> CustomFieldValueWithCfDto:
|
|
1222
|
+
"""Create a custom field value in a project.
|
|
1223
|
+
|
|
1224
|
+
Args:
|
|
1225
|
+
project_id: Target project ID.
|
|
1226
|
+
data: Custom field value payload.
|
|
1227
|
+
|
|
1228
|
+
Returns:
|
|
1229
|
+
The created custom field value DTO.
|
|
1230
|
+
|
|
1231
|
+
Raises:
|
|
1232
|
+
AllureNotFoundError: If project doesn't exist.
|
|
1233
|
+
AllureValidationError: If input data fails validation.
|
|
1234
|
+
AllureAuthError: If unauthorized.
|
|
1235
|
+
AllureAPIError: If the server returns an error.
|
|
1236
|
+
"""
|
|
1237
|
+
api = await self._get_api("_custom_field_value_project_api")
|
|
1238
|
+
|
|
1239
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
1240
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
1241
|
+
|
|
1242
|
+
try:
|
|
1243
|
+
return await self._call_api(
|
|
1244
|
+
api.create26(
|
|
1245
|
+
project_id=project_id,
|
|
1246
|
+
custom_field_value_project_create_dto=data,
|
|
1247
|
+
_request_timeout=self._timeout,
|
|
1248
|
+
)
|
|
1249
|
+
)
|
|
1250
|
+
except AllureAPIError as exc:
|
|
1251
|
+
if exc.status_code == 409:
|
|
1252
|
+
raise AllureValidationError(
|
|
1253
|
+
"Duplicate custom field value name.",
|
|
1254
|
+
status_code=exc.status_code,
|
|
1255
|
+
response_body=exc.response_body,
|
|
1256
|
+
suggestions=["Use a unique custom field value name"],
|
|
1257
|
+
) from exc
|
|
1258
|
+
raise
|
|
1259
|
+
|
|
1260
|
+
async def update_custom_field_value(
|
|
1261
|
+
self, project_id: int, cfv_id: int, data: CustomFieldValueProjectPatchDto
|
|
1262
|
+
) -> None:
|
|
1263
|
+
"""Update a custom field value in a project.
|
|
1264
|
+
|
|
1265
|
+
Args:
|
|
1266
|
+
project_id: Target project ID.
|
|
1267
|
+
cfv_id: Target custom field value ID.
|
|
1268
|
+
data: Patch payload for the custom field value.
|
|
1269
|
+
|
|
1270
|
+
Raises:
|
|
1271
|
+
AllureNotFoundError: If project or value doesn't exist.
|
|
1272
|
+
AllureValidationError: If input data fails validation.
|
|
1273
|
+
AllureAuthError: If unauthorized.
|
|
1274
|
+
AllureAPIError: If the server returns an error.
|
|
1275
|
+
"""
|
|
1276
|
+
api = await self._get_api("_custom_field_value_project_api")
|
|
1277
|
+
|
|
1278
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
1279
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
1280
|
+
if not isinstance(cfv_id, int) or cfv_id <= 0:
|
|
1281
|
+
raise AllureValidationError("Custom Field Value ID must be a positive integer")
|
|
1282
|
+
|
|
1283
|
+
await self._call_api(
|
|
1284
|
+
api.patch23(
|
|
1285
|
+
project_id=project_id,
|
|
1286
|
+
cfv_id=cfv_id,
|
|
1287
|
+
custom_field_value_project_patch_dto=data,
|
|
1288
|
+
_request_timeout=self._timeout,
|
|
1289
|
+
)
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
async def delete_custom_field_value(self, project_id: int, cfv_id: int) -> None:
|
|
1293
|
+
"""Delete a custom field value in a project.
|
|
1294
|
+
|
|
1295
|
+
Args:
|
|
1296
|
+
project_id: Target project ID.
|
|
1297
|
+
cfv_id: Target custom field value ID.
|
|
1298
|
+
|
|
1299
|
+
Raises:
|
|
1300
|
+
AllureNotFoundError: If project or value doesn't exist.
|
|
1301
|
+
AllureValidationError: If input data fails validation.
|
|
1302
|
+
AllureAuthError: If unauthorized.
|
|
1303
|
+
AllureAPIError: If the server returns an error.
|
|
1304
|
+
"""
|
|
1305
|
+
api = await self._get_api("_custom_field_value_project_api")
|
|
1306
|
+
|
|
1307
|
+
if not isinstance(project_id, int) or project_id <= 0:
|
|
1308
|
+
raise AllureValidationError("Project ID must be a positive integer")
|
|
1309
|
+
if not isinstance(cfv_id, int) or cfv_id <= 0:
|
|
1310
|
+
raise AllureValidationError("Custom Field Value ID must be a positive integer")
|
|
1311
|
+
|
|
1312
|
+
await self._call_api(
|
|
1313
|
+
api.delete47(
|
|
1314
|
+
project_id=project_id,
|
|
1315
|
+
id=cfv_id,
|
|
1316
|
+
_request_timeout=self._timeout,
|
|
1317
|
+
)
|
|
1318
|
+
)
|
|
1319
|
+
|
|
870
1320
|
async def get_custom_fields_with_values(self, project_id: int) -> list[CustomFieldProjectWithValuesDto]:
|
|
871
1321
|
"""Fetch all custom fields and their allowed values for a project.
|
|
872
1322
|
|
|
@@ -923,6 +1373,49 @@ class AllureClient:
|
|
|
923
1373
|
|
|
924
1374
|
return results
|
|
925
1375
|
|
|
1376
|
+
async def get_test_case_custom_fields(
|
|
1377
|
+
self,
|
|
1378
|
+
test_case_id: int,
|
|
1379
|
+
project_id: int,
|
|
1380
|
+
) -> list[CustomFieldProjectWithValuesDto]:
|
|
1381
|
+
"""Fetch custom fields with values for a specific test case.
|
|
1382
|
+
|
|
1383
|
+
Args:
|
|
1384
|
+
test_case_id: Target test case ID.
|
|
1385
|
+
project_id: The project ID context.
|
|
1386
|
+
|
|
1387
|
+
Returns:
|
|
1388
|
+
List of custom field DTOs with their assigned values.
|
|
1389
|
+
"""
|
|
1390
|
+
api = await self._get_api("_test_case_custom_field_api")
|
|
1391
|
+
return await self._call_api(
|
|
1392
|
+
api.get_custom_fields_with_values3(
|
|
1393
|
+
test_case_id=test_case_id,
|
|
1394
|
+
project_id=project_id,
|
|
1395
|
+
_request_timeout=self._timeout,
|
|
1396
|
+
)
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
async def update_cfvs_of_test_case(
|
|
1400
|
+
self,
|
|
1401
|
+
test_case_id: int,
|
|
1402
|
+
custom_fields: list[CustomFieldValueWithCfDto],
|
|
1403
|
+
) -> None:
|
|
1404
|
+
"""Update custom field values for a test case.
|
|
1405
|
+
|
|
1406
|
+
Args:
|
|
1407
|
+
test_case_id: Target test case ID.
|
|
1408
|
+
custom_fields: List of custom field DTOs with new values.
|
|
1409
|
+
"""
|
|
1410
|
+
api = await self._get_api("_test_case_custom_field_api")
|
|
1411
|
+
await self._call_api(
|
|
1412
|
+
api.update_cfvs_of_test_case(
|
|
1413
|
+
test_case_id=test_case_id,
|
|
1414
|
+
custom_field_with_values_dto=custom_fields,
|
|
1415
|
+
_request_timeout=self._timeout,
|
|
1416
|
+
)
|
|
1417
|
+
)
|
|
1418
|
+
|
|
926
1419
|
async def delete_scenario_step(self, step_id: int) -> None:
|
|
927
1420
|
"""Delete a scenario step.
|
|
928
1421
|
|
|
@@ -933,14 +1426,14 @@ class AllureClient:
|
|
|
933
1426
|
AllureAPIError: If the API request fails.
|
|
934
1427
|
"""
|
|
935
1428
|
scenario_api = await self._get_api("_scenario_api")
|
|
936
|
-
await self.
|
|
937
|
-
scenario_api.
|
|
1429
|
+
await self._call_api_raw(
|
|
1430
|
+
scenario_api.delete_by_id1_without_preload_content(
|
|
938
1431
|
id=step_id,
|
|
939
1432
|
_request_timeout=self._timeout,
|
|
940
1433
|
)
|
|
941
1434
|
)
|
|
942
1435
|
|
|
943
|
-
async def get_test_case(self, test_case_id: int) ->
|
|
1436
|
+
async def get_test_case(self, test_case_id: int) -> TestCaseDtoWithCF:
|
|
944
1437
|
"""Retrieve a specific test case by its ID.
|
|
945
1438
|
|
|
946
1439
|
Args:
|
|
@@ -973,6 +1466,8 @@ class AllureClient:
|
|
|
973
1466
|
)
|
|
974
1467
|
if overview.custom_fields:
|
|
975
1468
|
case.custom_fields = overview.custom_fields
|
|
1469
|
+
if overview.issues:
|
|
1470
|
+
case.issues = overview.issues
|
|
976
1471
|
except Exception as e:
|
|
977
1472
|
logger.warning(f"Failed to fetch overview for test case {test_case_id}: {e}")
|
|
978
1473
|
|
|
@@ -1041,7 +1536,6 @@ class AllureClient:
|
|
|
1041
1536
|
)
|
|
1042
1537
|
)
|
|
1043
1538
|
raw_data = self._extract_response_data(response)
|
|
1044
|
-
# print(f"DEBUG Initial Raw Normalized Scenario: {raw_data}")
|
|
1045
1539
|
return self._denormalize_to_v2_from_dict(raw_data)
|
|
1046
1540
|
|
|
1047
1541
|
@staticmethod
|
|
@@ -1114,13 +1608,14 @@ class AllureClient:
|
|
|
1114
1608
|
child_ids = step_def.get("children") or []
|
|
1115
1609
|
child_steps = build_steps(child_ids) if child_ids else None
|
|
1116
1610
|
|
|
1117
|
-
# Build
|
|
1611
|
+
# Build StepWithExpected
|
|
1118
1612
|
steps_list.append(
|
|
1119
1613
|
SharedStepScenarioDtoStepsInner(
|
|
1120
|
-
actual_instance=
|
|
1614
|
+
actual_instance=StepWithExpected.model_construct(
|
|
1121
1615
|
type="BodyStepDto",
|
|
1122
1616
|
body=body,
|
|
1123
1617
|
body_json=None, # Skip complex rich-text
|
|
1618
|
+
expected_result=step_def.get("expectedResult"),
|
|
1124
1619
|
steps=child_steps,
|
|
1125
1620
|
id=sid,
|
|
1126
1621
|
)
|
|
@@ -1219,6 +1714,38 @@ class AllureClient:
|
|
|
1219
1714
|
|
|
1220
1715
|
return await self._create_scenario_step_via_api(shared_step_scenario_api, step)
|
|
1221
1716
|
|
|
1717
|
+
async def patch_test_case_scenario_step(
|
|
1718
|
+
self,
|
|
1719
|
+
step_id: int,
|
|
1720
|
+
patch: ScenarioStepPatchDto,
|
|
1721
|
+
) -> None:
|
|
1722
|
+
"""Patch a specific scenario step within a test case."""
|
|
1723
|
+
scenario_api = await self._get_api("_scenario_api")
|
|
1724
|
+
await self._call_api_raw(
|
|
1725
|
+
scenario_api.patch_by_id_without_preload_content(
|
|
1726
|
+
id=step_id,
|
|
1727
|
+
scenario_step_patch_dto=patch,
|
|
1728
|
+
with_expected_result=False,
|
|
1729
|
+
_request_timeout=self._timeout,
|
|
1730
|
+
)
|
|
1731
|
+
)
|
|
1732
|
+
|
|
1733
|
+
async def patch_shared_step_scenario_step(
|
|
1734
|
+
self,
|
|
1735
|
+
step_id: int,
|
|
1736
|
+
patch: ScenarioStepPatchDto,
|
|
1737
|
+
) -> None:
|
|
1738
|
+
"""Patch a specific scenario step within a shared step."""
|
|
1739
|
+
shared_step_scenario_api = await self._get_api("_shared_step_scenario_api")
|
|
1740
|
+
await self._call_api_raw(
|
|
1741
|
+
shared_step_scenario_api.patch_by_id1_without_preload_content(
|
|
1742
|
+
id=step_id,
|
|
1743
|
+
scenario_step_patch_dto=patch,
|
|
1744
|
+
with_expected_result=False,
|
|
1745
|
+
_request_timeout=self._timeout,
|
|
1746
|
+
)
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1222
1749
|
async def upload_shared_step_attachment(
|
|
1223
1750
|
self,
|
|
1224
1751
|
shared_step_id: int,
|
|
@@ -1306,3 +1833,21 @@ class AllureClient:
|
|
|
1306
1833
|
shared_step_api = await self._get_api("_shared_step_api")
|
|
1307
1834
|
# Soft delete via archive
|
|
1308
1835
|
await self._call_api(shared_step_api.archive(id=shared_step_id, _request_timeout=self._timeout))
|
|
1836
|
+
|
|
1837
|
+
async def update_test_case_custom_fields(
|
|
1838
|
+
self, test_case_id: int, custom_fields: list[CustomFieldValueWithCfDto]
|
|
1839
|
+
) -> None:
|
|
1840
|
+
"""Update custom field values of a test case using dedicated endpoint.
|
|
1841
|
+
|
|
1842
|
+
Args:
|
|
1843
|
+
test_case_id: Target test case ID.
|
|
1844
|
+
custom_fields: List of custom fields with values to set/clear.
|
|
1845
|
+
"""
|
|
1846
|
+
api = await self._get_api("_test_case_custom_field_api")
|
|
1847
|
+
await self._call_api(
|
|
1848
|
+
api.update_cfvs_of_test_case(
|
|
1849
|
+
test_case_id=test_case_id,
|
|
1850
|
+
custom_field_with_values_dto=custom_fields,
|
|
1851
|
+
_request_timeout=self._timeout,
|
|
1852
|
+
)
|
|
1853
|
+
)
|