lucius-mcp 0.2.2__py3-none-any.whl → 0.3.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.
Files changed (38) hide show
  1. {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.3.0.dist-info}/METADATA +8 -1
  2. {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.3.0.dist-info}/RECORD +38 -19
  3. src/client/__init__.py +10 -0
  4. src/client/client.py +289 -8
  5. src/client/generated/README.md +11 -0
  6. src/client/generated/__init__.py +4 -0
  7. src/client/generated/api/__init__.py +2 -0
  8. src/client/generated/api/test_layer_controller_api.py +1746 -0
  9. src/client/generated/api/test_layer_schema_controller_api.py +1415 -0
  10. src/client/generated/docs/TestLayerControllerApi.md +407 -0
  11. src/client/generated/docs/TestLayerSchemaControllerApi.md +350 -0
  12. src/client/overridden/test_case_custom_fields_v2.py +254 -0
  13. src/services/__init__.py +8 -0
  14. src/services/launch_service.py +278 -0
  15. src/services/search_service.py +1 -1
  16. src/services/test_case_service.py +512 -92
  17. src/services/test_layer_service.py +416 -0
  18. src/tools/__init__.py +35 -0
  19. src/tools/create_test_case.py +38 -19
  20. src/tools/create_test_layer.py +33 -0
  21. src/tools/create_test_layer_schema.py +39 -0
  22. src/tools/delete_test_layer.py +31 -0
  23. src/tools/delete_test_layer_schema.py +31 -0
  24. src/tools/get_custom_fields.py +2 -1
  25. src/tools/get_test_case_custom_fields.py +34 -0
  26. src/tools/launches.py +112 -0
  27. src/tools/list_test_layer_schemas.py +43 -0
  28. src/tools/list_test_layers.py +38 -0
  29. src/tools/search.py +6 -3
  30. src/tools/test_layers.py +21 -0
  31. src/tools/update_test_case.py +48 -23
  32. src/tools/update_test_layer.py +33 -0
  33. src/tools/update_test_layer_schema.py +40 -0
  34. src/utils/__init__.py +4 -0
  35. src/utils/links.py +13 -0
  36. {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.3.0.dist-info}/WHEEL +0 -0
  37. {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.3.0.dist-info}/entry_points.txt +0 -0
  38. {lucius_mcp-0.2.2.dist-info → lucius_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,416 @@
1
+ """Service for managing Test Layers in Allure TestOps."""
2
+
3
+ import logging
4
+
5
+ from pydantic import ValidationError as PydanticValidationError
6
+
7
+ from src.client import AllureClient
8
+ from src.client.exceptions import AllureAPIError, AllureNotFoundError, AllureValidationError
9
+ from src.client.generated.exceptions import ApiException
10
+ from src.client.generated.models.page_test_layer_dto import PageTestLayerDto
11
+ from src.client.generated.models.page_test_layer_schema_dto import PageTestLayerSchemaDto
12
+ from src.client.generated.models.test_layer_create_dto import TestLayerCreateDto
13
+ from src.client.generated.models.test_layer_dto import TestLayerDto
14
+ from src.client.generated.models.test_layer_patch_dto import TestLayerPatchDto
15
+ from src.client.generated.models.test_layer_schema_create_dto import TestLayerSchemaCreateDto
16
+ from src.client.generated.models.test_layer_schema_dto import TestLayerSchemaDto
17
+ from src.client.generated.models.test_layer_schema_patch_dto import TestLayerSchemaPatchDto
18
+ from src.utils.schema_hint import generate_schema_hint
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Constants - from Allure TestOps API constraints
23
+ # These limits match the database schema VARCHAR field lengths for test layer names and schema keys
24
+ MAX_NAME_LENGTH = 255
25
+ MAX_KEY_LENGTH = 255
26
+
27
+
28
+ class TestLayerService:
29
+ """Service for managing Test Layers and their Schemas in Allure TestOps."""
30
+
31
+ def __init__(self, client: AllureClient) -> None:
32
+ """Initialize TestLayerService.
33
+
34
+ Args:
35
+ client: AllureClient instance
36
+ """
37
+ self._client = client
38
+ self._project_id = client.get_project()
39
+
40
+ # ==========================================
41
+ # Test Layer CRUD Operations
42
+ # ==========================================
43
+
44
+ async def list_test_layers(
45
+ self,
46
+ page: int = 0,
47
+ size: int = 100,
48
+ ) -> list[TestLayerDto]:
49
+ """List all test layers.
50
+
51
+ Args:
52
+ page: Page number (0-based)
53
+ size: Page size
54
+
55
+ Returns:
56
+ List of TestLayerDto
57
+
58
+ Raises:
59
+ AllureAPIError: If the API request fails
60
+ """
61
+ if not self._client._test_layer_api:
62
+ raise AllureAPIError("Test Layer API is not initialized")
63
+
64
+ try:
65
+ page_dto: PageTestLayerDto = await self._client._test_layer_api.find_all7(
66
+ page=page,
67
+ size=size,
68
+ )
69
+ return page_dto.content or []
70
+ except Exception as e:
71
+ raise AllureAPIError(f"Failed to list test layers: {e}") from e
72
+
73
+ async def create_test_layer(self, name: str) -> TestLayerDto:
74
+ """Create a new test layer.
75
+
76
+ Args:
77
+ name: Name of the test layer
78
+
79
+ Returns:
80
+ The created TestLayerDto
81
+
82
+ Raises:
83
+ AllureValidationError: If validation fails
84
+ AllureAPIError: If the API request fails
85
+ """
86
+ self._validate_name(name)
87
+
88
+ if not self._client._test_layer_api:
89
+ raise AllureAPIError("Test Layer API is not initialized")
90
+
91
+ try:
92
+ create_dto = TestLayerCreateDto(name=name)
93
+ except PydanticValidationError as e:
94
+ hint = generate_schema_hint(TestLayerCreateDto)
95
+ raise AllureValidationError(f"Invalid test layer data: {e}", suggestions=[hint]) from e
96
+
97
+ try:
98
+ return await self._client._test_layer_api.create9(test_layer_create_dto=create_dto)
99
+ except Exception as e:
100
+ raise AllureAPIError(
101
+ f"Failed to create test layer '{name}': {e}. "
102
+ "Ensure the name is unique and you have project permissions."
103
+ ) from e
104
+
105
+ async def get_test_layer(self, layer_id: int) -> TestLayerDto:
106
+ """Get a test layer by ID.
107
+
108
+ Args:
109
+ layer_id: The test layer ID
110
+
111
+ Returns:
112
+ The TestLayerDto
113
+
114
+ Raises:
115
+ AllureNotFoundError: If test layer doesn't exist
116
+ AllureAPIError: If the API request fails
117
+ """
118
+ if not self._client._test_layer_api:
119
+ raise AllureAPIError("Test Layer API is not initialized")
120
+
121
+ try:
122
+ return await self._client._test_layer_api.find_one8(id=layer_id)
123
+ except ApiException as e:
124
+ self._client._handle_api_exception(e)
125
+ raise
126
+ except Exception as e:
127
+ raise AllureAPIError(f"Failed to get test layer {layer_id}: {e}") from e
128
+
129
+ async def update_test_layer(
130
+ self,
131
+ layer_id: int,
132
+ name: str,
133
+ ) -> tuple[TestLayerDto, bool]:
134
+ """Update an existing test layer with idempotency support.
135
+
136
+ Args:
137
+ layer_id: The test layer ID to update
138
+ name: New name for the test layer
139
+
140
+ Returns:
141
+ Tuple of (updated_test_layer, changed) where changed is True if update was applied
142
+
143
+ Raises:
144
+ AllureNotFoundError: If test layer doesn't exist
145
+ AllureValidationError: If validation fails
146
+ AllureAPIError: If the API request fails
147
+ """
148
+ self._validate_name(name)
149
+
150
+ # Get current state
151
+ current = await self.get_test_layer(layer_id)
152
+
153
+ # Check idempotency
154
+ if current.name == name:
155
+ return current, False
156
+
157
+ if not self._client._test_layer_api:
158
+ raise AllureAPIError("Test Layer API is not initialized")
159
+
160
+ # Build patch DTO and update
161
+ try:
162
+ patch_data = TestLayerPatchDto(name=name)
163
+ except PydanticValidationError as e:
164
+ hint = generate_schema_hint(TestLayerPatchDto)
165
+ raise AllureValidationError(f"Invalid patch data: {e}", suggestions=[hint]) from e
166
+
167
+ try:
168
+ updated = await self._client._test_layer_api.patch9(id=layer_id, test_layer_patch_dto=patch_data)
169
+ return updated, True
170
+ except Exception as e:
171
+ raise AllureAPIError(
172
+ f"Failed to update test layer {layer_id}: {e}. "
173
+ "Check that the layer exists and you have update permissions."
174
+ ) from e
175
+
176
+ async def delete_test_layer(self, layer_id: int) -> bool:
177
+ """Delete a test layer with idempotent behavior.
178
+
179
+ Args:
180
+ layer_id: The test layer ID to delete
181
+
182
+ Returns:
183
+ True if the layer was deleted, False if it was already deleted/not found
184
+
185
+ Raises:
186
+ AllureAPIError: If the API request fails (other than 404)
187
+
188
+ Note:
189
+ This operation is idempotent. If already deleted (404), returns False.
190
+ """
191
+ if not self._client._test_layer_api:
192
+ raise AllureAPIError("Test Layer API is not initialized")
193
+
194
+ try:
195
+ await self._client._test_layer_api.delete9(id=layer_id)
196
+ return True
197
+ except AllureNotFoundError:
198
+ # Idempotent: if already deleted, this is fine
199
+ logger.debug(f"Test layer {layer_id} already deleted or not found")
200
+ return False
201
+
202
+ # ==========================================
203
+ # Test Layer Schema CRUD Operations
204
+ # ==========================================
205
+
206
+ async def list_test_layer_schemas(
207
+ self,
208
+ project_id: int | None = None,
209
+ page: int = 0,
210
+ size: int = 100,
211
+ ) -> list[TestLayerSchemaDto]:
212
+ """List all test layer schemas for a project.
213
+
214
+ Args:
215
+ project_id: Project ID (defaults to client's project)
216
+ page: Page number (0-based)
217
+ size: Page size
218
+
219
+ Returns:
220
+ List of TestLayerSchemaDto
221
+
222
+ Raises:
223
+ AllureAPIError: If the API request fails
224
+ """
225
+ target_project_id = project_id or self._project_id
226
+ self._validate_project_id(target_project_id)
227
+
228
+ if not self._client._test_layer_schema_api:
229
+ raise AllureAPIError("Test Layer Schema API is not initialized")
230
+
231
+ try:
232
+ page_dto: PageTestLayerSchemaDto = await self._client._test_layer_schema_api.find_all6(
233
+ project_id=target_project_id,
234
+ page=page,
235
+ size=size,
236
+ )
237
+ return page_dto.content or []
238
+ except Exception as e:
239
+ raise AllureAPIError(f"Failed to list test layer schemas: {e}") from e
240
+
241
+ async def create_test_layer_schema(
242
+ self,
243
+ project_id: int,
244
+ test_layer_id: int,
245
+ key: str,
246
+ ) -> TestLayerSchemaDto:
247
+ """Create a new test layer schema.
248
+
249
+ Args:
250
+ project_id: The project ID
251
+ test_layer_id: The test layer ID to link to
252
+ key: The schema key
253
+
254
+ Returns:
255
+ The created TestLayerSchemaDto
256
+
257
+ Raises:
258
+ AllureValidationError: If validation fails
259
+ AllureAPIError: If the API request fails
260
+ """
261
+ self._validate_project_id(project_id)
262
+ self._validate_key(key)
263
+
264
+ if not self._client._test_layer_schema_api:
265
+ raise AllureAPIError("Test Layer Schema API is not initialized")
266
+
267
+ try:
268
+ create_dto = TestLayerSchemaCreateDto(
269
+ project_id=project_id,
270
+ test_layer_id=test_layer_id,
271
+ key=key,
272
+ )
273
+ except PydanticValidationError as e:
274
+ hint = generate_schema_hint(TestLayerSchemaCreateDto)
275
+ raise AllureValidationError(f"Invalid test layer schema data: {e}", suggestions=[hint]) from e
276
+
277
+ try:
278
+ return await self._client._test_layer_schema_api.create8(test_layer_schema_create_dto=create_dto)
279
+ except Exception as e:
280
+ raise AllureAPIError(
281
+ f"Failed to create test layer schema '{key}': {e}. "
282
+ "Ensure the project_id and test_layer_id are valid, and the key is unique within the project."
283
+ ) from e
284
+
285
+ async def get_test_layer_schema(self, schema_id: int) -> TestLayerSchemaDto:
286
+ """Get a test layer schema by ID.
287
+
288
+ Args:
289
+ schema_id: The test layer schema ID
290
+
291
+ Returns:
292
+ The TestLayerSchemaDto
293
+
294
+ Raises:
295
+ AllureNotFoundError: If test layer schema doesn't exist
296
+ AllureAPIError: If the API request fails
297
+ """
298
+ if not self._client._test_layer_schema_api:
299
+ raise AllureAPIError("Test Layer Schema API is not initialized")
300
+
301
+ try:
302
+ return await self._client._test_layer_schema_api.find_one7(id=schema_id)
303
+ except Exception as e:
304
+ raise AllureAPIError(f"Failed to get test layer schema {schema_id}: {e}") from e
305
+
306
+ async def update_test_layer_schema(
307
+ self,
308
+ schema_id: int,
309
+ test_layer_id: int | None = None,
310
+ key: str | None = None,
311
+ ) -> tuple[TestLayerSchemaDto, bool]:
312
+ """Update an existing test layer schema with idempotency support.
313
+
314
+ Args:
315
+ schema_id: The test layer schema ID to update
316
+ test_layer_id: New test layer ID (optional)
317
+ key: New key (optional)
318
+
319
+ Returns:
320
+ Tuple of (updated_schema, changed) where changed is True if update was applied
321
+
322
+ Raises:
323
+ AllureNotFoundError: If test layer schema doesn't exist
324
+ AllureValidationError: If validation fails
325
+ AllureAPIError: If the API request fails
326
+ """
327
+ # Validation
328
+ if key is not None:
329
+ self._validate_key(key)
330
+
331
+ # Get current state
332
+ current = await self.get_test_layer_schema(schema_id)
333
+
334
+ # Check idempotency
335
+ needs_update = False
336
+ if test_layer_id is not None and current.test_layer and current.test_layer.id != test_layer_id:
337
+ needs_update = True
338
+ if key is not None and current.key != key:
339
+ needs_update = True
340
+
341
+ # No changes needed
342
+ if not needs_update:
343
+ return current, False
344
+
345
+ if not self._client._test_layer_schema_api:
346
+ raise AllureAPIError("Test Layer Schema API is not initialized")
347
+
348
+ # Build patch DTO and update
349
+ try:
350
+ patch_data = TestLayerSchemaPatchDto(
351
+ test_layer_id=test_layer_id,
352
+ key=key,
353
+ )
354
+ except PydanticValidationError as e:
355
+ hint = generate_schema_hint(TestLayerSchemaPatchDto)
356
+ raise AllureValidationError(f"Invalid patch data: {e}", suggestions=[hint]) from e
357
+
358
+ try:
359
+ updated = await self._client._test_layer_schema_api.patch8(
360
+ id=schema_id, test_layer_schema_patch_dto=patch_data
361
+ )
362
+ return updated, True
363
+ except Exception as e:
364
+ raise AllureAPIError(
365
+ f"Failed to update test layer schema {schema_id}: {e}. "
366
+ "Check that the schema exists and the new values are valid."
367
+ ) from e
368
+
369
+ async def delete_test_layer_schema(self, schema_id: int) -> bool:
370
+ """Delete a test layer schema with idempotent behavior.
371
+
372
+ Args:
373
+ schema_id: The test layer schema ID to delete
374
+
375
+ Returns:
376
+ True if the schema was deleted, False if it was already deleted/not found
377
+
378
+ Raises:
379
+ AllureAPIError: If the API request fails (other than 404)
380
+
381
+ Note:
382
+ This operation is idempotent. If already deleted (404), returns False.
383
+ """
384
+ if not self._client._test_layer_schema_api:
385
+ raise AllureAPIError("Test Layer Schema API is not initialized")
386
+
387
+ try:
388
+ await self._client._test_layer_schema_api.delete8(id=schema_id)
389
+ return True
390
+ except AllureNotFoundError:
391
+ # Idempotent: if already deleted, this is fine
392
+ logger.debug(f"Test layer schema {schema_id} already deleted or not found")
393
+ return False
394
+
395
+ # ==========================================
396
+ # Validation Methods
397
+ # ==========================================
398
+
399
+ def _validate_project_id(self, project_id: int) -> None:
400
+ """Validate project ID."""
401
+ if not isinstance(project_id, int) or project_id <= 0:
402
+ raise AllureValidationError("Project ID must be a positive integer")
403
+
404
+ def _validate_name(self, name: str) -> None:
405
+ """Validate test layer name."""
406
+ if not name or not name.strip():
407
+ raise AllureValidationError("Name is required")
408
+ if len(name) > MAX_NAME_LENGTH:
409
+ raise AllureValidationError(f"Name too long (max {MAX_NAME_LENGTH})")
410
+
411
+ def _validate_key(self, key: str) -> None:
412
+ """Validate test layer schema key."""
413
+ if not key or not key.strip():
414
+ raise AllureValidationError("Key is required")
415
+ if len(key) > MAX_KEY_LENGTH:
416
+ raise AllureValidationError(f"Key too long (max {MAX_KEY_LENGTH})")
src/tools/__init__.py CHANGED
@@ -3,26 +3,49 @@ from collections.abc import Awaitable, Callable
3
3
  from src.tools.create_test_case import create_test_case
4
4
  from src.tools.delete_test_case import delete_test_case
5
5
  from src.tools.get_custom_fields import get_custom_fields
6
+ from src.tools.get_test_case_custom_fields import get_test_case_custom_fields
7
+ from src.tools.launches import create_launch, list_launches
6
8
  from src.tools.link_shared_step import link_shared_step
7
9
  from src.tools.search import get_test_case_details, list_test_cases, search_test_cases
8
10
  from src.tools.shared_steps import create_shared_step, delete_shared_step, list_shared_steps, update_shared_step
11
+ from src.tools.test_layers import (
12
+ create_test_layer,
13
+ create_test_layer_schema,
14
+ delete_test_layer,
15
+ delete_test_layer_schema,
16
+ list_test_layer_schemas,
17
+ list_test_layers,
18
+ update_test_layer,
19
+ update_test_layer_schema,
20
+ )
9
21
  from src.tools.unlink_shared_step import unlink_shared_step
10
22
  from src.tools.update_test_case import update_test_case
11
23
 
12
24
  __all__ = [
25
+ "create_launch",
13
26
  "create_shared_step",
14
27
  "create_test_case",
28
+ "create_test_layer",
29
+ "create_test_layer_schema",
15
30
  "delete_shared_step",
16
31
  "delete_test_case",
32
+ "delete_test_layer",
33
+ "delete_test_layer_schema",
17
34
  "get_custom_fields",
35
+ "get_test_case_custom_fields",
18
36
  "get_test_case_details",
19
37
  "link_shared_step",
38
+ "list_launches",
20
39
  "list_shared_steps",
21
40
  "list_test_cases",
41
+ "list_test_layer_schemas",
42
+ "list_test_layers",
22
43
  "search_test_cases",
23
44
  "unlink_shared_step",
24
45
  "update_shared_step",
25
46
  "update_test_case",
47
+ "update_test_layer",
48
+ "update_test_layer_schema",
26
49
  ]
27
50
 
28
51
  ToolFn = Callable[..., Awaitable[object]]
@@ -34,11 +57,23 @@ all_tools: list[ToolFn] = [
34
57
  delete_test_case,
35
58
  list_test_cases,
36
59
  get_custom_fields,
60
+ get_test_case_custom_fields,
37
61
  search_test_cases,
62
+ create_launch,
63
+ list_launches,
38
64
  create_shared_step,
39
65
  list_shared_steps,
40
66
  update_shared_step,
41
67
  delete_shared_step,
42
68
  link_shared_step,
43
69
  unlink_shared_step,
70
+ # Test Layer Tools
71
+ list_test_layers,
72
+ create_test_layer,
73
+ update_test_layer,
74
+ delete_test_layer,
75
+ list_test_layer_schemas,
76
+ create_test_layer_schema,
77
+ update_test_layer_schema,
78
+ delete_test_layer_schema,
44
79
  ]
@@ -1,6 +1,6 @@
1
1
  """Tool for creating Test Cases in Allure TestOps."""
2
2
 
3
- from typing import Annotated, Any
3
+ from typing import Annotated
4
4
 
5
5
  from pydantic import Field
6
6
 
@@ -12,7 +12,7 @@ async def create_test_case(
12
12
  name: Annotated[str, Field(description="The name of the test case.")],
13
13
  description: Annotated[str | None, Field(description="A markdown description of the test case.")] = None,
14
14
  steps: Annotated[
15
- list[dict[str, Any]] | None,
15
+ list[dict[str, object]] | None,
16
16
  Field(
17
17
  description="List of steps. Each step must be a dict with 'action' and 'expected' keys. "
18
18
  "Example: [{'action': 'Login', 'expected': 'Dashboard visible'}]"
@@ -29,10 +29,28 @@ async def create_test_case(
29
29
  ),
30
30
  ] = None,
31
31
  custom_fields: Annotated[
32
- dict[str, str] | None,
32
+ dict[str, str | list[str]] | None,
33
33
  Field(
34
- description="Dictionary of custom field names and their values."
35
- "Example: {'Layer': 'UI', 'Component': 'Auth'}"
34
+ description="Dictionary of custom field names and their values (string or list of strings)."
35
+ "Example: {'Layer': 'UI', 'Components': ['Auth', 'DB']}"
36
+ ),
37
+ ] = None,
38
+ test_layer_id: Annotated[
39
+ int | None,
40
+ Field(
41
+ description=(
42
+ "Optional test layer ID to assign (use list_test_layers to find IDs). "
43
+ "If provided, the layer must exist in the project."
44
+ )
45
+ ),
46
+ ] = None,
47
+ test_layer_name: Annotated[
48
+ str | None,
49
+ Field(
50
+ description=(
51
+ "Optional test layer name to assign (exact case-sensitive match). "
52
+ "Mutually exclusive with test_layer_id."
53
+ )
36
54
  ),
37
55
  ] = None,
38
56
  project_id: Annotated[int | None, Field(description="Optional override for the default Project ID.")] = None,
@@ -49,8 +67,10 @@ async def create_test_case(
49
67
  Example Base64: [{'name': 's.png', 'content': '<base64>', 'content_type': 'image/png'}]
50
68
  Example URL: [{'name': 'report.pdf', 'url': 'http://example.com/report.pdf',
51
69
  'content_type': 'application/pdf'}]
52
- custom_fields: Dictionary of custom field names and their values.
53
- Example: {'Layer': 'UI', 'Component': 'Auth'}
70
+ custom_fields: Dictionary of custom field names and their values (string or list of strings).
71
+ Example: {'Layer': 'UI', 'Components': ['Auth', 'DB']}
72
+ test_layer_id: Optional test layer ID to assign (must exist in the project).
73
+ test_layer_name: Optional test layer name to assign (exact case-sensitive match).
54
74
  project_id: Optional override for the default Project ID.
55
75
 
56
76
  Returns:
@@ -62,15 +82,14 @@ async def create_test_case(
62
82
 
63
83
  async with AllureClient.from_env(project=project_id) as client:
64
84
  service = TestCaseService(client=client)
65
- try:
66
- result = await service.create_test_case(
67
- name=name,
68
- description=description,
69
- steps=steps,
70
- tags=tags,
71
- attachments=attachments,
72
- custom_fields=custom_fields,
73
- )
74
- return f"Created Test Case ID: {result.id} Name: {result.name}"
75
- except Exception as e:
76
- return f"Error creating test case: {e}"
85
+ result = await service.create_test_case(
86
+ name=name,
87
+ description=description,
88
+ steps=steps,
89
+ tags=tags,
90
+ attachments=attachments,
91
+ custom_fields=custom_fields,
92
+ test_layer_id=test_layer_id,
93
+ test_layer_name=test_layer_name,
94
+ )
95
+ return f"Created Test Case ID: {result.id} Name: {result.name}"
@@ -0,0 +1,33 @@
1
+ """Tool for creating a test layer."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+
7
+ from src.client import AllureClient
8
+ from src.services.test_layer_service import TestLayerService
9
+
10
+
11
+ async def create_test_layer(
12
+ name: Annotated[str, Field(description="Name of the test layer (e.g., 'Unit', 'Integration', 'E2E').")],
13
+ project_id: Annotated[
14
+ int | None, Field(description="Allure TestOps project ID to create the test layer in.")
15
+ ] = None,
16
+ ) -> str:
17
+ """Create a new test layer in Allure TestOps.
18
+
19
+ Test layers define taxonomy for categorizing test cases. Common examples include
20
+ 'Unit', 'Integration', 'E2E', 'UI', 'API', etc.
21
+
22
+ Args:
23
+ name: Name of the test layer
24
+ project_id: Optional project ID override
25
+
26
+ Returns:
27
+ Confirmation message with the created layer's ID and name
28
+ """
29
+ async with AllureClient.from_env(project=project_id) as client:
30
+ service = TestLayerService(client)
31
+ layer = await service.create_test_layer(name=name)
32
+
33
+ return f"✅ Test layer created successfully! ID: {layer.id}, Name: {layer.name}"
@@ -0,0 +1,39 @@
1
+ """Tool for creating a test layer schema."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+
7
+ from src.client import AllureClient
8
+ from src.services.test_layer_service import TestLayerService
9
+
10
+
11
+ async def create_test_layer_schema(
12
+ key: Annotated[str, Field(description="The schema key (e.g., custom field name like 'layer' or 'test_layer').")],
13
+ test_layer_id: Annotated[int, Field(description="ID of the test layer to link to this schema.")],
14
+ project_id: Annotated[int, Field(description="Allure TestOps project ID to create the schema in.")],
15
+ ) -> str:
16
+ """Create a new test layer schema to map a custom field key to a test layer.
17
+
18
+ Test layer schemas define the mapping between custom field keys and test layers.
19
+ This allows test cases with specific custom field values to be automatically
20
+ assigned to the correct test layer.
21
+
22
+ Args:
23
+ key: Schema key (typically a custom field name)
24
+ test_layer_id: ID of the test layer to link
25
+ project_id: Project ID
26
+
27
+ Returns:
28
+ Confirmation message with the created schema's details
29
+ """
30
+ async with AllureClient.from_env(project=project_id) as client:
31
+ service = TestLayerService(client)
32
+ schema = await service.create_test_layer_schema(
33
+ project_id=project_id,
34
+ test_layer_id=test_layer_id,
35
+ key=key,
36
+ )
37
+
38
+ test_layer_name = schema.test_layer.name if schema.test_layer else "N/A"
39
+ return f"✅ Test layer schema created successfully! ID: {schema.id}, Key: {schema.key}, Layer: {test_layer_name}"
@@ -0,0 +1,31 @@
1
+ """Tool for deleting a test layer."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+
7
+ from src.client import AllureClient
8
+ from src.services.test_layer_service import TestLayerService
9
+
10
+
11
+ async def delete_test_layer(
12
+ layer_id: Annotated[int, Field(description="ID of the test layer to delete.")],
13
+ project_id: Annotated[int | None, Field(description="Allure TestOps project ID.")] = None,
14
+ ) -> str:
15
+ """Delete a test layer from Allure TestOps.
16
+
17
+ Args:
18
+ layer_id: ID of the test layer to delete
19
+ project_id: Optional project ID override
20
+
21
+ Returns:
22
+ Confirmation message
23
+ """
24
+ async with AllureClient.from_env(project=project_id) as client:
25
+ service = TestLayerService(client)
26
+ deleted = await service.delete_test_layer(layer_id=layer_id)
27
+
28
+ if deleted:
29
+ return f"✅ Test layer {layer_id} deleted successfully!"
30
+ else:
31
+ return f"[INFO] Test layer {layer_id} was already deleted or doesn't exist - no action taken."