aio-tests-mcp 0.1.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.
Files changed (54) hide show
  1. aio_tests_mcp-0.1.0/.gitignore +19 -0
  2. aio_tests_mcp-0.1.0/.kiro/settings/mcp.json +15 -0
  3. aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/.config.kiro +1 -0
  4. aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/design.md +496 -0
  5. aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/requirements.md +181 -0
  6. aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/tasks.md +236 -0
  7. aio_tests_mcp-0.1.0/PKG-INFO +50 -0
  8. aio_tests_mcp-0.1.0/README.md +31 -0
  9. aio_tests_mcp-0.1.0/aio_tests_mcp/__init__.py +3 -0
  10. aio_tests_mcp-0.1.0/aio_tests_mcp/config.py +93 -0
  11. aio_tests_mcp-0.1.0/aio_tests_mcp/logging.py +87 -0
  12. aio_tests_mcp-0.1.0/aio_tests_mcp/models/__init__.py +90 -0
  13. aio_tests_mcp-0.1.0/aio_tests_mcp/models/attachment.py +42 -0
  14. aio_tests_mcp-0.1.0/aio_tests_mcp/models/common.py +47 -0
  15. aio_tests_mcp-0.1.0/aio_tests_mcp/models/execution.py +76 -0
  16. aio_tests_mcp-0.1.0/aio_tests_mcp/models/folder.py +63 -0
  17. aio_tests_mcp-0.1.0/aio_tests_mcp/models/test_case.py +62 -0
  18. aio_tests_mcp-0.1.0/aio_tests_mcp/models/test_cycle.py +50 -0
  19. aio_tests_mcp-0.1.0/aio_tests_mcp/models/traceability.py +35 -0
  20. aio_tests_mcp-0.1.0/aio_tests_mcp/models/versioning.py +50 -0
  21. aio_tests_mcp-0.1.0/aio_tests_mcp/server.py +984 -0
  22. aio_tests_mcp-0.1.0/aio_tests_mcp/services/__init__.py +9 -0
  23. aio_tests_mcp-0.1.0/aio_tests_mcp/services/aio_client.py +454 -0
  24. aio_tests_mcp-0.1.0/aio_tests_mcp/services/project_resolver.py +64 -0
  25. aio_tests_mcp-0.1.0/aio_tests_mcp/templates/__init__.py +11 -0
  26. aio_tests_mcp-0.1.0/aio_tests_mcp/templates/folder_templates.py +185 -0
  27. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/__init__.py +15 -0
  28. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/attachments.py +147 -0
  29. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/executions.py +223 -0
  30. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/folders.py +448 -0
  31. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/test_cases.py +326 -0
  32. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/test_cycles.py +321 -0
  33. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/traceability.py +162 -0
  34. aio_tests_mcp-0.1.0/aio_tests_mcp/tools/versioning.py +281 -0
  35. aio_tests_mcp-0.1.0/pyproject.toml +45 -0
  36. aio_tests_mcp-0.1.0/run_aio_mcp.sh +6 -0
  37. aio_tests_mcp-0.1.0/tests/__init__.py +0 -0
  38. aio_tests_mcp-0.1.0/tests/conftest.py +15 -0
  39. aio_tests_mcp-0.1.0/tests/test_aio_client.py +579 -0
  40. aio_tests_mcp-0.1.0/tests/test_config.py +120 -0
  41. aio_tests_mcp-0.1.0/tests/test_folder_templates.py +230 -0
  42. aio_tests_mcp-0.1.0/tests/test_logging.py +159 -0
  43. aio_tests_mcp-0.1.0/tests/test_models_common.py +159 -0
  44. aio_tests_mcp-0.1.0/tests/test_models_entities.py +686 -0
  45. aio_tests_mcp-0.1.0/tests/test_models_test_case.py +234 -0
  46. aio_tests_mcp-0.1.0/tests/test_project_resolver.py +97 -0
  47. aio_tests_mcp-0.1.0/tests/test_tools_attachments.py +373 -0
  48. aio_tests_mcp-0.1.0/tests/test_tools_executions.py +581 -0
  49. aio_tests_mcp-0.1.0/tests/test_tools_folders.py +600 -0
  50. aio_tests_mcp-0.1.0/tests/test_tools_test_cases.py +570 -0
  51. aio_tests_mcp-0.1.0/tests/test_tools_test_cycles.py +609 -0
  52. aio_tests_mcp-0.1.0/tests/test_tools_traceability.py +497 -0
  53. aio_tests_mcp-0.1.0/tests/test_tools_versioning.py +642 -0
  54. aio_tests_mcp-0.1.0/uv.lock +993 -0
@@ -0,0 +1,19 @@
1
+ # Secrets
2
+ .env
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ .venv/
9
+ dist/
10
+ *.egg-info/
11
+
12
+ # Testing
13
+ .pytest_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
@@ -0,0 +1,15 @@
1
+ {
2
+ "mcpServers": {
3
+ "aio-tests": {
4
+ "command": "/Users/rsharma/Downloads/Projects/MCP Servers/.venv/bin/python",
5
+ "args": ["-m", "aio_tests_mcp.server"],
6
+ "env": {
7
+ "AIO_API_TOKEN": "${AIO_API_TOKEN}",
8
+ "AIO_PROJECT_KEY": "BSTDV001"
9
+ },
10
+ "cwd": "/Users/rsharma/Downloads/Projects/MCP Servers",
11
+ "disabled": false,
12
+ "autoApprove": []
13
+ }
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ {"specId": "8219d27b-870f-4463-9ed9-45cd09685596", "workflowType": "requirements-first", "specType": "feature"}
@@ -0,0 +1,496 @@
1
+ # Design Document: AIO Tests MCP Server
2
+
3
+ ## Overview
4
+
5
+ This document describes the technical design for an MCP server that wraps the AIO Tests for Jira Cloud REST API. The server exposes test management operations (test cases, cycles, executions, folders, attachments, traceability, versioning) as MCP tools consumable by AI-powered IDEs.
6
+
7
+ The server is built in Python using the FastMCP framework and deployed as a shared service using SSE (Server-Sent Events) transport. A single deployment serves the entire organization, authenticated via a service account API token.
8
+
9
+ ### Key Design Decisions
10
+
11
+ 1. **FastMCP over raw MCP SDK**: FastMCP provides decorator-based tool registration, automatic schema generation from type hints, and built-in SSE transport — reducing boilerplate significantly.
12
+ 2. **Single shared deployment**: One server instance with a service account token serves all users. Project scoping is per-request, not per-connection.
13
+ 3. **Async HTTP client (httpx)**: All AIO API calls use `httpx.AsyncClient` for non-blocking I/O, connection pooling, and timeout management.
14
+ 4. **Retry with tenacity**: Exponential backoff for 429/5xx responses using the `tenacity` library rather than hand-rolled retry logic.
15
+ 5. **Pydantic models for validation**: All tool inputs are validated via Pydantic models before any API call is made, providing clear error messages.
16
+ 6. **No bulk deletes by design**: Destructive operations are limited to single-item scope as a safety guardrail against accidental mass data loss from AI agents.
17
+
18
+ ## Architecture
19
+
20
+ ```mermaid
21
+ graph TB
22
+ subgraph "AI IDEs"
23
+ K[Kiro]
24
+ C[Cursor]
25
+ O[Other MCP Clients]
26
+ end
27
+
28
+ subgraph "MCP Server (Shared Deployment)"
29
+ F[FastMCP Framework<br/>SSE Transport]
30
+ T[Tool Layer<br/>Input Validation]
31
+ S[Service Layer<br/>Business Logic]
32
+ AC[API Client<br/>httpx + Retry]
33
+ L[Structured Logger<br/>JSON + Correlation IDs]
34
+ end
35
+
36
+ subgraph "External Services"
37
+ AIO[AIO Tests Cloud API<br/>tcms.aiojiraapps.com]
38
+ FS[Local Filesystem<br/>Attachment Upload/Download]
39
+ end
40
+
41
+ K -->|SSE/MCP| F
42
+ C -->|SSE/MCP| F
43
+ O -->|SSE/MCP| F
44
+ F --> T
45
+ T --> S
46
+ S --> AC
47
+ AC -->|HTTPS + AioAuth| AIO
48
+ S --> FS
49
+ T --> L
50
+ S --> L
51
+ AC --> L
52
+ ```
53
+
54
+ ### Request Flow
55
+
56
+ ```mermaid
57
+ sequenceDiagram
58
+ participant Client as MCP Client (IDE)
59
+ participant MCP as FastMCP Server
60
+ participant Tool as Tool Function
61
+ participant Service as Service Layer
62
+ participant API as AIO Tests API
63
+
64
+ Client->>MCP: Invoke tool (SSE)
65
+ MCP->>Tool: Dispatch to tool function
66
+ Tool->>Tool: Validate input (Pydantic)
67
+ alt Validation fails
68
+ Tool-->>Client: Return validation error
69
+ end
70
+ Tool->>Service: Call service method
71
+ Service->>Service: Resolve project key
72
+ Service->>API: HTTPS request (AioAuth header)
73
+ alt 429 or 5xx
74
+ API-->>Service: Error response
75
+ Service->>Service: Retry with backoff (up to 3x)
76
+ Service->>API: Retry request
77
+ end
78
+ API-->>Service: Success response
79
+ Service-->>Tool: Parsed response
80
+ Tool-->>Client: Formatted result
81
+ ```
82
+
83
+ ## Components and Interfaces
84
+
85
+ ### Package Structure
86
+
87
+ ```
88
+ aio_tests_mcp/
89
+ ├── __init__.py
90
+ ├── server.py # FastMCP server initialization, entry point
91
+ ├── config.py # Configuration from environment variables
92
+ ├── models/
93
+ │ ├── __init__.py
94
+ │ ├── test_case.py # Test case Pydantic models
95
+ │ ├── test_cycle.py # Test cycle Pydantic models
96
+ │ ├── execution.py # Execution Pydantic models
97
+ │ ├── folder.py # Folder Pydantic models
98
+ │ ├── attachment.py # Attachment Pydantic models
99
+ │ ├── traceability.py # Traceability link models
100
+ │ ├── versioning.py # Version history models
101
+ │ └── common.py # Shared models (pagination, errors)
102
+ ├── tools/
103
+ │ ├── __init__.py
104
+ │ ├── test_cases.py # Test case CRUD tools
105
+ │ ├── test_cycles.py # Test cycle management tools
106
+ │ ├── executions.py # Execution management tools
107
+ │ ├── folders.py # Folder management tools
108
+ │ ├── attachments.py # Attachment tools
109
+ │ ├── traceability.py # Traceability link tools
110
+ │ └── versioning.py # Version history tools
111
+ ├── services/
112
+ │ ├── __init__.py
113
+ │ ├── aio_client.py # HTTP client with retry logic
114
+ │ └── project_resolver.py # Project key resolution logic
115
+ ├── templates/
116
+ │ ├── __init__.py
117
+ │ └── folder_templates.py # Predefined folder structures
118
+ └── logging.py # Structured JSON logging setup
119
+ ```
120
+
121
+ ### Component Responsibilities
122
+
123
+ | Component | Responsibility |
124
+ |-----------|---------------|
125
+ | `server.py` | FastMCP initialization, tool registration, SSE transport startup |
126
+ | `config.py` | Read and validate environment variables, expose typed config |
127
+ | `tools/*` | MCP tool functions with `@mcp.tool` decorators, input validation |
128
+ | `services/aio_client.py` | HTTP client wrapper with auth, retry, error mapping |
129
+ | `services/project_resolver.py` | Resolve project key from parameter or default |
130
+ | `models/*` | Pydantic models for request/response validation |
131
+ | `templates/folder_templates.py` | Predefined folder hierarchy definitions |
132
+ | `logging.py` | Structured JSON logger with correlation ID injection |
133
+
134
+ ### Key Interfaces
135
+
136
+ #### AIOClient (services/aio_client.py)
137
+
138
+ ```python
139
+ class AIOClient:
140
+ """Async HTTP client for AIO Tests Cloud API with retry and auth."""
141
+
142
+ def __init__(self, api_token: str, base_url: str = "https://tcms.aiojiraapps.com/aio-tcms/api/v1/") -> None: ...
143
+
144
+ async def get(self, path: str, params: dict | None = None) -> dict: ...
145
+ async def post(self, path: str, json: dict | None = None) -> dict: ...
146
+ async def put(self, path: str, json: dict | None = None) -> dict: ...
147
+ async def delete(self, path: str) -> dict: ...
148
+ async def upload(self, path: str, file_path: Path) -> dict: ...
149
+ async def download(self, path: str, dest_path: Path) -> Path: ...
150
+ async def close(self) -> None: ...
151
+ ```
152
+
153
+ #### ProjectResolver (services/project_resolver.py)
154
+
155
+ ```python
156
+ class ProjectResolver:
157
+ """Resolves project key from explicit parameter or default config."""
158
+
159
+ def __init__(self, default_project_key: str | None) -> None: ...
160
+
161
+ def resolve(self, project_key: str | None) -> str:
162
+ """Return project_key if provided, else default, else raise error."""
163
+ ...
164
+ ```
165
+
166
+ #### Tool Function Signature Pattern
167
+
168
+ ```python
169
+ @mcp.tool
170
+ async def create_test_case(
171
+ title: str,
172
+ description: str = "",
173
+ folder_id: str | None = None,
174
+ priority: str | None = None,
175
+ case_type: str | None = None,
176
+ project_key: str | None = None,
177
+ ) -> dict:
178
+ """Create a new test case in AIO Tests.
179
+
180
+ Args:
181
+ title: Test case title (required)
182
+ description: Test case description
183
+ folder_id: Target folder ID for organization
184
+ priority: Priority level (Critical, High, Medium, Low)
185
+ case_type: Type of test (Functional, Integration, Unit, etc.)
186
+ project_key: Jira project key (uses default if not provided)
187
+
188
+ Returns:
189
+ Created test case with key and metadata
190
+ """
191
+ ...
192
+ ```
193
+
194
+ ## Data Models
195
+
196
+ ### Configuration
197
+
198
+ ```python
199
+ class ServerConfig(BaseModel):
200
+ """Server configuration from environment variables."""
201
+ aio_api_token: str # Required: AIO_API_TOKEN
202
+ aio_project_key: str | None = None # Optional: AIO_PROJECT_KEY
203
+ aio_base_url: str = "https://tcms.aiojiraapps.com/aio-tcms/api/v1/"
204
+ server_host: str = "0.0.0.0"
205
+ server_port: int = 8000
206
+ log_level: str = "INFO"
207
+ max_retries: int = 3
208
+ retry_base_delay: float = 1.0 # seconds
209
+ request_timeout: float = 30.0 # seconds
210
+ ```
211
+
212
+ ### Test Case Models
213
+
214
+ ```python
215
+ class CreateTestCaseInput(BaseModel):
216
+ """Input for creating a test case."""
217
+ title: str = Field(..., min_length=1, max_length=500)
218
+ description: str = ""
219
+ folder_id: str | None = None
220
+ priority: str | None = None
221
+ case_type: str | None = None
222
+ status: str | None = None
223
+ automation_status: str | None = None
224
+ project_key: str | None = None
225
+
226
+ class TestCaseResponse(BaseModel):
227
+ """Response from AIO Tests for a test case."""
228
+ key: str
229
+ title: str
230
+ description: str | None = None
231
+ folder_id: str | None = None
232
+ priority: str | None = None
233
+ case_type: str | None = None
234
+ status: str | None = None
235
+ created_by: str | None = None
236
+ created_on: str | None = None
237
+ ```
238
+
239
+ ### Pagination Models
240
+
241
+ ```python
242
+ class PaginationParams(BaseModel):
243
+ """Pagination parameters for list operations."""
244
+ page: int = Field(default=1, ge=1)
245
+ page_size: int = Field(default=25, ge=1, le=100)
246
+
247
+ class PaginatedResponse(BaseModel):
248
+ """Paginated response wrapper."""
249
+ items: list[dict]
250
+ total_count: int
251
+ current_page: int
252
+ total_pages: int
253
+ page_size: int
254
+ ```
255
+
256
+ ### Folder Template Models
257
+
258
+ ```python
259
+ class FolderNode(BaseModel):
260
+ """A node in a folder hierarchy template."""
261
+ name: str
262
+ children: list["FolderNode"] = []
263
+
264
+ class FolderTemplate(BaseModel):
265
+ """A predefined folder structure template."""
266
+ template_id: str
267
+ name: str
268
+ description: str
269
+ root_nodes: list[FolderNode]
270
+ ```
271
+
272
+ ### Versioning Models
273
+
274
+ ```python
275
+ class VersionEntry(BaseModel):
276
+ """A single version in a test case's history."""
277
+ version_number: int
278
+ author: str | None = None
279
+ timestamp: str | None = None
280
+ change_summary: str | None = None
281
+
282
+ class VersionDiff(BaseModel):
283
+ """Differences between two versions of a test case."""
284
+ test_case_key: str
285
+ from_version: int
286
+ to_version: int
287
+ differences: list[FieldDiff]
288
+
289
+ class FieldDiff(BaseModel):
290
+ """A single field difference between versions."""
291
+ field_name: str
292
+ old_value: str | None = None
293
+ new_value: str | None = None
294
+ ```
295
+
296
+ ### Error Models
297
+
298
+ ```python
299
+ class AIOError(BaseModel):
300
+ """Structured error response."""
301
+ error_code: str
302
+ message: str
303
+ correlation_id: str
304
+ details: dict | None = None
305
+ ```
306
+
307
+ ## Correctness Properties
308
+
309
+ *A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
310
+
311
+ ### Property 1: Correlation ID Universality
312
+
313
+ *For any* tool invocation (successful or failed), the server SHALL include a unique correlation ID in all log entries produced during that invocation AND in any error response returned to the client.
314
+
315
+ **Validates: Requirements 1.6, 8.6**
316
+
317
+ ### Property 2: Create Operations Return Identifiers
318
+
319
+ *For any* valid input to a create tool (create_test_case, create_test_cycle, create_test_execution, create_test_folder, create_test_case_version), the response SHALL contain a non-empty identifier (key or ID) for the created resource.
320
+
321
+ **Validates: Requirements 2.1, 3.1, 4.1, 5.1, 13.3**
322
+
323
+ ### Property 3: Update Operations Send Only Specified Fields
324
+
325
+ *For any* update tool invocation (update_test_case, update_test_cycle, update_test_execution) with a subset of updatable fields, the API request body SHALL contain only the fields explicitly provided by the caller — no unspecified fields shall be included in the request payload.
326
+
327
+ **Validates: Requirements 2.3, 3.3, 4.2**
328
+
329
+ ### Property 4: Pagination Metadata Consistency
330
+
331
+ *For any* list tool response with pagination, the following invariants SHALL hold: `total_pages == ceil(total_count / page_size)`, `current_page == requested_page`, `len(items) <= page_size`, and if `current_page < total_pages` then `len(items) == page_size`.
332
+
333
+ **Validates: Requirements 2.5, 3.4, 11.1, 11.2, 13.7**
334
+
335
+ ### Property 5: Bulk Operations Return One Result Per Input
336
+
337
+ *For any* bulk operation (bulk_update_executions, bulk_organize_cases) with N input items, the response SHALL contain exactly N result entries, one for each input item.
338
+
339
+ **Validates: Requirements 4.3, 5.6**
340
+
341
+ ### Property 6: Partial Failure Isolation
342
+
343
+ *For any* bulk operation containing a mix of valid and invalid items, all valid items SHALL be processed successfully regardless of which other items fail, and each failed item SHALL have an individual error report.
344
+
345
+ **Validates: Requirements 4.5**
346
+
347
+ ### Property 7: Template Expansion Correctness
348
+
349
+ *For any* folder template, invoking `create_folder_structure` SHALL result in exactly as many folder creation API calls as there are nodes in the template's hierarchy, and the parent-child relationships in the API calls SHALL match the template's structure.
350
+
351
+ **Validates: Requirements 5.4**
352
+
353
+ ### Property 8: Folder Suggestion Validity
354
+
355
+ *For any* non-empty set of test case keys, the `suggest_folder_structure` tool SHALL return a valid tree structure where every node has a name, no node is orphaned (all non-root nodes have a parent in the tree), and nesting depth does not exceed 4 levels.
356
+
357
+ **Validates: Requirements 5.5, 5.7**
358
+
359
+ ### Property 9: Retry with Exponential Backoff
360
+
361
+ *For any* API request that receives a retryable error (HTTP 429 or 5xx), the server SHALL retry up to 3 times with delays that increase exponentially (delay_n >= delay_base * 2^(n-1)), and SHALL return a service unavailable error only after all retries are exhausted.
362
+
363
+ **Validates: Requirements 8.3, 8.4**
364
+
365
+ ### Property 10: Input Validation Before API Call
366
+
367
+ *For any* tool invocation with invalid input (missing required fields, values outside allowed ranges, malformed identifiers), the server SHALL return a descriptive validation error WITHOUT making any HTTP request to the AIO Tests API.
368
+
369
+ **Validates: Requirements 8.5**
370
+
371
+ ### Property 11: Project Key Resolution
372
+
373
+ *For any* tool invocation that includes an explicit `project_key` parameter, the API request SHALL use that project key in the request path, regardless of whether a default project key is configured.
374
+
375
+ **Validates: Requirements 9.1, 9.2**
376
+
377
+ ### Property 12: Version History Ordering
378
+
379
+ *For any* response from `get_version_history`, the version entries SHALL be ordered by version_number in ascending order (oldest first), and each version_number SHALL be unique within the list.
380
+
381
+ **Validates: Requirements 13.1**
382
+
383
+ ### Property 13: Version Diff Accuracy
384
+
385
+ *For any* two distinct versions of a test case, the `compare_versions` response SHALL list only fields where the values actually differ between the two versions, and for each listed difference, `old_value` SHALL match the field's value in the earlier version and `new_value` SHALL match the later version.
386
+
387
+ **Validates: Requirements 13.4**
388
+
389
+ ## Error Handling
390
+
391
+ ### Error Classification and Response Strategy
392
+
393
+ | HTTP Status | Error Code | User Message | Action |
394
+ |-------------|-----------|--------------|--------|
395
+ | 400 | `VALIDATION_ERROR` | Descriptive field-level errors | Return immediately, no retry |
396
+ | 401 | `AUTH_INVALID` | "API token is invalid or expired" | Return immediately, no retry |
397
+ | 403 | `PERMISSION_DENIED` | "Insufficient permissions for this operation" | Return immediately, no retry |
398
+ | 404 | `NOT_FOUND` | "Resource not found: {resource_type} {identifier}" | Return immediately, no retry |
399
+ | 429 | `RATE_LIMITED` | "Rate limited, retrying..." → "Rate limit exceeded after retries" | Retry with exponential backoff |
400
+ | 5xx | `SERVICE_ERROR` | "AIO Tests service error, retrying..." → "Service unavailable" | Retry with exponential backoff |
401
+ | Network error | `CONNECTION_ERROR` | "Unable to reach AIO Tests API" | Retry with exponential backoff |
402
+
403
+ ### Retry Strategy
404
+
405
+ ```python
406
+ # Retry configuration
407
+ MAX_RETRIES = 3
408
+ BASE_DELAY = 1.0 # seconds
409
+ MAX_DELAY = 30.0 # seconds
410
+ RETRYABLE_STATUSES = {429, 500, 502, 503, 504}
411
+
412
+ # Delay calculation: min(base * 2^attempt, max_delay) + jitter
413
+ ```
414
+
415
+ ### Input Validation Rules
416
+
417
+ All validation happens at the tool layer before any API call:
418
+
419
+ - **Required string fields**: Must be non-empty after stripping whitespace
420
+ - **Identifiers (keys)**: Must match expected format (e.g., project key pattern `^[A-Z][A-Z0-9_]+$`)
421
+ - **Pagination**: `page >= 1`, `1 <= page_size <= 100`
422
+ - **File paths**: Must exist on filesystem before upload attempt
423
+ - **Lists for bulk operations**: Must be non-empty, max 50 items per call
424
+
425
+ ### Security Considerations
426
+
427
+ - **Token never logged**: The `AIO_API_TOKEN` value is never included in log output, error messages, or MCP responses. Only the presence/absence of the token is logged.
428
+ - **HTTPS only**: All communication with `tcms.aiojiraapps.com` uses HTTPS. The client rejects non-TLS connections.
429
+ - **Input sanitization**: All user-provided strings are validated for length and character content before being passed to the API.
430
+ - **No credential echo**: Error responses for 401 never include the token value, only that it's invalid.
431
+ - **Correlation IDs are opaque**: UUIDs used for correlation, no user data embedded.
432
+
433
+ ## Testing Strategy
434
+
435
+ ### Testing Approach
436
+
437
+ This project uses a dual testing approach:
438
+
439
+ 1. **Property-based tests** (using `hypothesis`): Verify the 13 correctness properties above hold across randomly generated inputs. Each property test runs a minimum of 100 iterations.
440
+ 2. **Unit tests** (using `pytest`): Cover specific examples, edge cases, error mappings, and integration points.
441
+ 3. **Integration tests**: Verify the server starts, registers tools, and handles real HTTP flows against a mock API.
442
+
443
+ ### Property-Based Testing Configuration
444
+
445
+ - **Library**: `hypothesis` (Python)
446
+ - **Minimum iterations**: 100 per property
447
+ - **Tag format**: `# Feature: aio-tests-mcp-server, Property {N}: {title}`
448
+
449
+ Each correctness property maps to one property-based test:
450
+
451
+ | Property | Test File | What's Generated |
452
+ |----------|-----------|-----------------|
453
+ | 1: Correlation ID | `tests/test_logging_properties.py` | Random tool names and inputs |
454
+ | 2: Create returns ID | `tests/test_crud_properties.py` | Random valid creation inputs |
455
+ | 3: Update sends only specified | `tests/test_crud_properties.py` | Random field subsets |
456
+ | 4: Pagination consistency | `tests/test_pagination_properties.py` | Random page/page_size/total_count |
457
+ | 5: Bulk result count | `tests/test_bulk_properties.py` | Random-length input lists |
458
+ | 6: Partial failure isolation | `tests/test_bulk_properties.py` | Random valid/invalid ID mixes |
459
+ | 7: Template expansion | `tests/test_folder_properties.py` | Random template structures |
460
+ | 8: Suggestion validity | `tests/test_folder_properties.py` | Random test case metadata sets |
461
+ | 9: Retry backoff | `tests/test_retry_properties.py` | Random retryable status sequences |
462
+ | 10: Validation before API | `tests/test_validation_properties.py` | Random invalid inputs |
463
+ | 11: Project key resolution | `tests/test_project_properties.py` | Random project keys |
464
+ | 12: Version ordering | `tests/test_versioning_properties.py` | Random version lists |
465
+ | 13: Diff accuracy | `tests/test_versioning_properties.py` | Random version state pairs |
466
+
467
+ ### Unit Test Coverage
468
+
469
+ - All tool functions: happy path with mocked API responses
470
+ - Error mapping: each HTTP error status → correct error code
471
+ - Edge cases: empty inputs, missing project key, non-existent files
472
+ - Configuration: startup with/without env vars
473
+ - Destructive operation safety: verify no bulk delete tools exist
474
+
475
+ ### Test Infrastructure
476
+
477
+ - **Mocking**: `httpx` responses mocked via `respx` or `pytest-httpx`
478
+ - **Fixtures**: Shared fixtures for `AIOClient`, `ServerConfig`, mock API responses
479
+ - **Markers**: `@pytest.mark.property` for PBT, `@pytest.mark.integration` for integration tests
480
+
481
+ ### Running Tests
482
+
483
+ ```bash
484
+ # All tests
485
+ pytest tests/ -v
486
+
487
+ # Property tests only
488
+ pytest tests/ -v -m property
489
+
490
+ # With coverage
491
+ pytest tests/ --cov=aio_tests_mcp --cov-report=term-missing
492
+
493
+ # Property tests with more examples
494
+ pytest tests/ -v -m property --hypothesis-seed=0
495
+ ```
496
+