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.
- aio_tests_mcp-0.1.0/.gitignore +19 -0
- aio_tests_mcp-0.1.0/.kiro/settings/mcp.json +15 -0
- aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/.config.kiro +1 -0
- aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/design.md +496 -0
- aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/requirements.md +181 -0
- aio_tests_mcp-0.1.0/.kiro/specs/aio-tests-mcp-server/tasks.md +236 -0
- aio_tests_mcp-0.1.0/PKG-INFO +50 -0
- aio_tests_mcp-0.1.0/README.md +31 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/__init__.py +3 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/config.py +93 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/logging.py +87 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/__init__.py +90 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/attachment.py +42 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/common.py +47 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/execution.py +76 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/folder.py +63 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/test_case.py +62 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/test_cycle.py +50 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/traceability.py +35 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/models/versioning.py +50 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/server.py +984 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/services/__init__.py +9 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/services/aio_client.py +454 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/services/project_resolver.py +64 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/templates/__init__.py +11 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/templates/folder_templates.py +185 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/__init__.py +15 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/attachments.py +147 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/executions.py +223 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/folders.py +448 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/test_cases.py +326 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/test_cycles.py +321 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/traceability.py +162 -0
- aio_tests_mcp-0.1.0/aio_tests_mcp/tools/versioning.py +281 -0
- aio_tests_mcp-0.1.0/pyproject.toml +45 -0
- aio_tests_mcp-0.1.0/run_aio_mcp.sh +6 -0
- aio_tests_mcp-0.1.0/tests/__init__.py +0 -0
- aio_tests_mcp-0.1.0/tests/conftest.py +15 -0
- aio_tests_mcp-0.1.0/tests/test_aio_client.py +579 -0
- aio_tests_mcp-0.1.0/tests/test_config.py +120 -0
- aio_tests_mcp-0.1.0/tests/test_folder_templates.py +230 -0
- aio_tests_mcp-0.1.0/tests/test_logging.py +159 -0
- aio_tests_mcp-0.1.0/tests/test_models_common.py +159 -0
- aio_tests_mcp-0.1.0/tests/test_models_entities.py +686 -0
- aio_tests_mcp-0.1.0/tests/test_models_test_case.py +234 -0
- aio_tests_mcp-0.1.0/tests/test_project_resolver.py +97 -0
- aio_tests_mcp-0.1.0/tests/test_tools_attachments.py +373 -0
- aio_tests_mcp-0.1.0/tests/test_tools_executions.py +581 -0
- aio_tests_mcp-0.1.0/tests/test_tools_folders.py +600 -0
- aio_tests_mcp-0.1.0/tests/test_tools_test_cases.py +570 -0
- aio_tests_mcp-0.1.0/tests/test_tools_test_cycles.py +609 -0
- aio_tests_mcp-0.1.0/tests/test_tools_traceability.py +497 -0
- aio_tests_mcp-0.1.0/tests/test_tools_versioning.py +642 -0
- aio_tests_mcp-0.1.0/uv.lock +993 -0
|
@@ -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
|
+
|