alita-sdk 0.3.155__py3-none-any.whl → 0.3.157__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.
- alita_sdk/tools/postman/__init__.py +1 -1
- alita_sdk/tools/postman/api_wrapper.py +762 -1243
- alita_sdk/tools/postman/postman_analysis.py +1133 -0
- {alita_sdk-0.3.155.dist-info → alita_sdk-0.3.157.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.155.dist-info → alita_sdk-0.3.157.dist-info}/RECORD +8 -7
- {alita_sdk-0.3.155.dist-info → alita_sdk-0.3.157.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.155.dist-info → alita_sdk-0.3.157.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.155.dist-info → alita_sdk-0.3.157.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,16 @@
|
|
1
|
+
import copy
|
1
2
|
import json
|
2
3
|
import logging
|
3
4
|
import re
|
4
|
-
from typing import Any, Dict, List, Optional,
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
5
6
|
from traceback import format_exc
|
6
7
|
|
7
8
|
import requests
|
8
9
|
from langchain_core.tools import ToolException
|
9
|
-
from pydantic import Field,
|
10
|
+
from pydantic import Field, model_validator, create_model, SecretStr
|
10
11
|
|
11
12
|
from ..elitea_base import BaseToolApiWrapper
|
13
|
+
from .postman_analysis import PostmanAnalyzer
|
12
14
|
|
13
15
|
logger = logging.getLogger(__name__)
|
14
16
|
|
@@ -18,21 +20,17 @@ PostmanGetCollections = create_model(
|
|
18
20
|
)
|
19
21
|
|
20
22
|
PostmanGetCollection = create_model(
|
21
|
-
"PostmanGetCollection"
|
22
|
-
collection_id=(str, Field(
|
23
|
-
description="The ID of the collection to retrieve"))
|
23
|
+
"PostmanGetCollection"
|
24
24
|
)
|
25
25
|
|
26
26
|
PostmanGetFolder = create_model(
|
27
27
|
"PostmanGetFolder",
|
28
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
29
28
|
folder_path=(str, Field(
|
30
29
|
description="The path to the folder (e.g., 'API/Users' for nested folders)"))
|
31
30
|
)
|
32
31
|
|
33
32
|
PostmanGetFolderRequests = create_model(
|
34
33
|
"PostmanGetFolderRequests",
|
35
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
36
34
|
folder_path=(str, Field(description="The path to the folder")),
|
37
35
|
include_details=(bool, Field(
|
38
36
|
description="Include detailed request information", default=False))
|
@@ -40,8 +38,6 @@ PostmanGetFolderRequests = create_model(
|
|
40
38
|
|
41
39
|
PostmanSearchRequests = create_model(
|
42
40
|
"PostmanSearchRequests",
|
43
|
-
collection_id=(str, Field(
|
44
|
-
description="The ID of the collection to search in")),
|
45
41
|
query=(str, Field(description="The search query")),
|
46
42
|
search_in=(str, Field(
|
47
43
|
description="Where to search: name, url, description, all", default="all")),
|
@@ -51,20 +47,22 @@ PostmanSearchRequests = create_model(
|
|
51
47
|
|
52
48
|
PostmanAnalyzeCollection = create_model(
|
53
49
|
"PostmanAnalyzeCollection",
|
54
|
-
|
55
|
-
description="
|
50
|
+
include_improvements=(bool, Field(
|
51
|
+
description="Include improvement suggestions in the analysis", default=False))
|
56
52
|
)
|
57
53
|
|
58
54
|
PostmanAnalyzeFolder = create_model(
|
59
55
|
"PostmanAnalyzeFolder",
|
60
|
-
|
61
|
-
|
56
|
+
folder_path=(str, Field(description="The path to the folder to analyze")),
|
57
|
+
include_improvements=(bool, Field(
|
58
|
+
description="Include improvement suggestions in the analysis", default=False))
|
62
59
|
)
|
63
60
|
|
64
|
-
|
65
|
-
"
|
66
|
-
|
67
|
-
|
61
|
+
PostmanAnalyzeRequest = create_model(
|
62
|
+
"PostmanAnalyzeRequest",
|
63
|
+
request_path=(str, Field(description="The path to the request to analyze")),
|
64
|
+
include_improvements=(bool, Field(
|
65
|
+
description="Include improvement suggestions in the analysis", default=False))
|
68
66
|
)
|
69
67
|
|
70
68
|
PostmanCreateCollection = create_model(
|
@@ -78,36 +76,37 @@ PostmanCreateCollection = create_model(
|
|
78
76
|
description="Optional default authentication", default=None))
|
79
77
|
)
|
80
78
|
|
81
|
-
|
82
|
-
"
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
79
|
+
PostmanUpdateCollectionName = create_model(
|
80
|
+
"PostmanUpdateCollectionName",
|
81
|
+
name=(str, Field(description="New name for the collection"))
|
82
|
+
)
|
83
|
+
|
84
|
+
PostmanUpdateCollectionDescription = create_model(
|
85
|
+
"PostmanUpdateCollectionDescription",
|
86
|
+
description=(str, Field(description="New description for the collection"))
|
87
|
+
)
|
88
|
+
|
89
|
+
PostmanUpdateCollectionVariables = create_model(
|
90
|
+
"PostmanUpdateCollectionVariables",
|
91
|
+
variables=(List[Dict], Field(description="Updated collection variables"))
|
92
|
+
)
|
93
|
+
|
94
|
+
PostmanUpdateCollectionAuth = create_model(
|
95
|
+
"PostmanUpdateCollectionAuth",
|
96
|
+
auth=(Dict, Field(description="Updated authentication settings"))
|
93
97
|
)
|
94
98
|
|
95
99
|
PostmanDeleteCollection = create_model(
|
96
|
-
"PostmanDeleteCollection"
|
97
|
-
collection_id=(str, Field(
|
98
|
-
description="The ID of the collection to delete"))
|
100
|
+
"PostmanDeleteCollection"
|
99
101
|
)
|
100
102
|
|
101
103
|
PostmanDuplicateCollection = create_model(
|
102
104
|
"PostmanDuplicateCollection",
|
103
|
-
collection_id=(str, Field(
|
104
|
-
description="The ID of the collection to duplicate")),
|
105
105
|
new_name=(str, Field(description="Name for the new collection copy"))
|
106
106
|
)
|
107
107
|
|
108
108
|
PostmanCreateFolder = create_model(
|
109
109
|
"PostmanCreateFolder",
|
110
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
111
110
|
name=(str, Field(description="Name of the new folder")),
|
112
111
|
description=(Optional[str], Field(
|
113
112
|
description="Optional description for the folder", default=None)),
|
@@ -119,7 +118,6 @@ PostmanCreateFolder = create_model(
|
|
119
118
|
|
120
119
|
PostmanUpdateFolder = create_model(
|
121
120
|
"PostmanUpdateFolder",
|
122
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
123
121
|
folder_path=(str, Field(description="Path to the folder to update")),
|
124
122
|
name=(Optional[str], Field(
|
125
123
|
description="New name for the folder", default=None)),
|
@@ -131,13 +129,11 @@ PostmanUpdateFolder = create_model(
|
|
131
129
|
|
132
130
|
PostmanDeleteFolder = create_model(
|
133
131
|
"PostmanDeleteFolder",
|
134
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
135
132
|
folder_path=(str, Field(description="Path to the folder to delete"))
|
136
133
|
)
|
137
134
|
|
138
135
|
PostmanMoveFolder = create_model(
|
139
136
|
"PostmanMoveFolder",
|
140
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
141
137
|
source_path=(str, Field(description="Current path of the folder to move")),
|
142
138
|
target_path=(Optional[str], Field(
|
143
139
|
description="New parent folder path", default=None))
|
@@ -145,7 +141,6 @@ PostmanMoveFolder = create_model(
|
|
145
141
|
|
146
142
|
PostmanCreateRequest = create_model(
|
147
143
|
"PostmanCreateRequest",
|
148
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
149
144
|
folder_path=(Optional[str], Field(
|
150
145
|
description="Path to the folder", default=None)),
|
151
146
|
name=(str, Field(description="Name of the new request")),
|
@@ -165,38 +160,67 @@ PostmanCreateRequest = create_model(
|
|
165
160
|
description="Optional pre-request script code", default=None))
|
166
161
|
)
|
167
162
|
|
168
|
-
|
169
|
-
"
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
163
|
+
PostmanUpdateRequestName = create_model(
|
164
|
+
"PostmanUpdateRequestName",
|
165
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
166
|
+
name=(str, Field(description="New name for the request"))
|
167
|
+
)
|
168
|
+
|
169
|
+
PostmanUpdateRequestMethod = create_model(
|
170
|
+
"PostmanUpdateRequestMethod",
|
171
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
172
|
+
method=(str, Field(description="HTTP method for the request"))
|
173
|
+
)
|
174
|
+
|
175
|
+
PostmanUpdateRequestUrl = create_model(
|
176
|
+
"PostmanUpdateRequestUrl",
|
177
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
178
|
+
url=(str, Field(description="URL for the request"))
|
179
|
+
)
|
180
|
+
|
181
|
+
PostmanUpdateRequestDescription = create_model(
|
182
|
+
"PostmanUpdateRequestDescription",
|
183
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
184
|
+
description=(str, Field(description="Description for the request"))
|
185
|
+
)
|
186
|
+
|
187
|
+
PostmanUpdateRequestHeaders = create_model(
|
188
|
+
"PostmanUpdateRequestHeaders",
|
189
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
190
|
+
headers=(List[Dict], Field(description="Request headers"))
|
191
|
+
)
|
192
|
+
|
193
|
+
PostmanUpdateRequestBody = create_model(
|
194
|
+
"PostmanUpdateRequestBody",
|
195
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
196
|
+
body=(Dict, Field(description="Request body"))
|
197
|
+
)
|
198
|
+
|
199
|
+
PostmanUpdateRequestAuth = create_model(
|
200
|
+
"PostmanUpdateRequestAuth",
|
201
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
202
|
+
auth=(Dict, Field(description="Request authentication"))
|
203
|
+
)
|
204
|
+
|
205
|
+
PostmanUpdateRequestTests = create_model(
|
206
|
+
"PostmanUpdateRequestTests",
|
207
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
208
|
+
tests=(str, Field(description="Test script code"))
|
209
|
+
)
|
210
|
+
|
211
|
+
PostmanUpdateRequestPreScript = create_model(
|
212
|
+
"PostmanUpdateRequestPreScript",
|
213
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
214
|
+
pre_request_script=(str, Field(description="Pre-request script code"))
|
189
215
|
)
|
190
216
|
|
191
217
|
PostmanDeleteRequest = create_model(
|
192
218
|
"PostmanDeleteRequest",
|
193
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
194
219
|
request_path=(str, Field(description="Path to the request to delete"))
|
195
220
|
)
|
196
221
|
|
197
222
|
PostmanDuplicateRequest = create_model(
|
198
223
|
"PostmanDuplicateRequest",
|
199
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
200
224
|
source_path=(str, Field(description="Path to the request to duplicate")),
|
201
225
|
new_name=(str, Field(description="Name for the duplicated request")),
|
202
226
|
target_path=(Optional[str], Field(
|
@@ -205,13 +229,24 @@ PostmanDuplicateRequest = create_model(
|
|
205
229
|
|
206
230
|
PostmanMoveRequest = create_model(
|
207
231
|
"PostmanMoveRequest",
|
208
|
-
collection_id=(str, Field(description="The ID of the collection")),
|
209
232
|
source_path=(str, Field(
|
210
233
|
description="Current path of the request to move")),
|
211
234
|
target_path=(Optional[str], Field(
|
212
235
|
description="New folder path", default=None))
|
213
236
|
)
|
214
237
|
|
238
|
+
PostmanGetRequest = create_model(
|
239
|
+
"PostmanGetRequest",
|
240
|
+
request_path=(str, Field(
|
241
|
+
description="The path to the request (e.g., 'API/Users/Get User' or 'applications/recommendations')"))
|
242
|
+
)
|
243
|
+
|
244
|
+
PostmanGetRequestScript = create_model(
|
245
|
+
"PostmanGetRequestScript",
|
246
|
+
request_path=(str, Field(description="Path to the request (folder/requestName)")),
|
247
|
+
script_type=(str, Field(description="The type of script to retrieve: 'test' or 'prerequest'", default="prerequest"))
|
248
|
+
)
|
249
|
+
|
215
250
|
|
216
251
|
class PostmanApiWrapper(BaseToolApiWrapper):
|
217
252
|
"""Wrapper for Postman API."""
|
@@ -221,7 +256,12 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
221
256
|
collection_id: Optional[str] = None
|
222
257
|
workspace_id: Optional[str] = None
|
223
258
|
timeout: int = 30
|
224
|
-
|
259
|
+
session: Any = None
|
260
|
+
analyzer: PostmanAnalyzer = None
|
261
|
+
|
262
|
+
model_config = {
|
263
|
+
"arbitrary_types_allowed": True
|
264
|
+
}
|
225
265
|
|
226
266
|
@model_validator(mode='before')
|
227
267
|
@classmethod
|
@@ -233,16 +273,14 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
233
273
|
"`requests` package not found, please run "
|
234
274
|
"`pip install requests`"
|
235
275
|
)
|
276
|
+
values["session"] = requests.Session()
|
277
|
+
values["session"].headers.update({
|
278
|
+
'X-API-Key': values.get('api_key'),
|
279
|
+
'Content-Type': 'application/json'
|
280
|
+
})
|
281
|
+
values["analyzer"] = PostmanAnalyzer()
|
236
282
|
return values
|
237
283
|
|
238
|
-
def __init__(self, **data):
|
239
|
-
super().__init__(**data)
|
240
|
-
self._session = requests.Session()
|
241
|
-
self._session.headers.update({
|
242
|
-
'X-API-Key': self.api_key.get_secret_value(),
|
243
|
-
'Content-Type': 'application/json',
|
244
|
-
})
|
245
|
-
# Removed ineffective timeout assignment. Timeout will be enforced in `_make_request`.
|
246
284
|
|
247
285
|
def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
|
248
286
|
"""Make HTTP request to Postman API."""
|
@@ -250,7 +288,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
250
288
|
|
251
289
|
try:
|
252
290
|
logger.info(f"Making {method.upper()} request to {url}")
|
253
|
-
response = self.
|
291
|
+
response = self.session.request(method, url, timeout=self.timeout, **kwargs)
|
254
292
|
response.raise_for_status()
|
255
293
|
|
256
294
|
if response.content:
|
@@ -258,8 +296,14 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
258
296
|
return {}
|
259
297
|
|
260
298
|
except requests.exceptions.RequestException as e:
|
261
|
-
|
262
|
-
|
299
|
+
error_details = ""
|
300
|
+
if hasattr(e, 'response') and e.response is not None:
|
301
|
+
try:
|
302
|
+
error_details = f" Response content: {e.response.text}"
|
303
|
+
except:
|
304
|
+
error_details = f" Response status: {e.response.status_code}"
|
305
|
+
logger.error(f"Request failed: {e}{error_details}")
|
306
|
+
raise ToolException(f"Postman API request failed: {str(e)}{error_details}")
|
263
307
|
except json.JSONDecodeError as e:
|
264
308
|
logger.error(f"Failed to decode JSON response: {e}")
|
265
309
|
raise ToolException(
|
@@ -296,6 +340,20 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
296
340
|
"args_schema": PostmanGetFolderRequests,
|
297
341
|
"ref": self.get_folder_requests
|
298
342
|
},
|
343
|
+
{
|
344
|
+
"name": "get_request",
|
345
|
+
"mode": "get_request",
|
346
|
+
"description": "Get a specific request by path",
|
347
|
+
"args_schema": PostmanGetRequest,
|
348
|
+
"ref": self.get_request
|
349
|
+
},
|
350
|
+
{
|
351
|
+
"name": "get_request_script",
|
352
|
+
"mode": "get_request_script",
|
353
|
+
"description": "Get the test or pre-request script content for a specific request",
|
354
|
+
"args_schema": PostmanGetRequestScript,
|
355
|
+
"ref": self.get_request_script
|
356
|
+
},
|
299
357
|
{
|
300
358
|
"name": "search_requests",
|
301
359
|
"mode": "search_requests",
|
@@ -318,11 +376,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
318
376
|
"ref": self.analyze_folder
|
319
377
|
},
|
320
378
|
{
|
321
|
-
"name": "
|
322
|
-
"mode": "
|
323
|
-
"description": "
|
324
|
-
"args_schema":
|
325
|
-
"ref": self.
|
379
|
+
"name": "analyze_request",
|
380
|
+
"mode": "analyze_request",
|
381
|
+
"description": "Analyze a specific request within a collection",
|
382
|
+
"args_schema": PostmanAnalyzeRequest,
|
383
|
+
"ref": self.analyze_request
|
326
384
|
},
|
327
385
|
{
|
328
386
|
"name": "create_collection",
|
@@ -332,19 +390,40 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
332
390
|
"ref": self.create_collection
|
333
391
|
},
|
334
392
|
{
|
335
|
-
"name": "
|
336
|
-
"mode": "
|
337
|
-
"description": "Update
|
338
|
-
"args_schema":
|
339
|
-
"ref": self.
|
393
|
+
"name": "update_collection_name",
|
394
|
+
"mode": "update_collection_name",
|
395
|
+
"description": "Update collection name",
|
396
|
+
"args_schema": PostmanUpdateCollectionName,
|
397
|
+
"ref": self.update_collection_name
|
398
|
+
},
|
399
|
+
{
|
400
|
+
"name": "update_collection_description",
|
401
|
+
"mode": "update_collection_description",
|
402
|
+
"description": "Update collection description",
|
403
|
+
"args_schema": PostmanUpdateCollectionDescription,
|
404
|
+
"ref": self.update_collection_description
|
405
|
+
},
|
406
|
+
{
|
407
|
+
"name": "update_collection_variables",
|
408
|
+
"mode": "update_collection_variables",
|
409
|
+
"description": "Update collection variables",
|
410
|
+
"args_schema": PostmanUpdateCollectionVariables,
|
411
|
+
"ref": self.update_collection_variables
|
340
412
|
},
|
341
413
|
{
|
342
|
-
"name": "
|
343
|
-
"mode": "
|
344
|
-
"description": "
|
345
|
-
"args_schema":
|
346
|
-
"ref": self.
|
414
|
+
"name": "update_collection_auth",
|
415
|
+
"mode": "update_collection_auth",
|
416
|
+
"description": "Update collection authentication settings",
|
417
|
+
"args_schema": PostmanUpdateCollectionAuth,
|
418
|
+
"ref": self.update_collection_auth
|
347
419
|
},
|
420
|
+
# {
|
421
|
+
# "name": "delete_collection",
|
422
|
+
# "mode": "delete_collection",
|
423
|
+
# "description": "Delete a collection permanently",
|
424
|
+
# "args_schema": PostmanDeleteCollection,
|
425
|
+
# "ref": self.delete_collection
|
426
|
+
# },
|
348
427
|
{
|
349
428
|
"name": "duplicate_collection",
|
350
429
|
"mode": "duplicate_collection",
|
@@ -367,13 +446,13 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
367
446
|
"args_schema": PostmanUpdateFolder,
|
368
447
|
"ref": self.update_folder
|
369
448
|
},
|
370
|
-
{
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
},
|
449
|
+
# {
|
450
|
+
# "name": "delete_folder",
|
451
|
+
# "mode": "delete_folder",
|
452
|
+
# "description": "Delete a folder and all its contents permanently",
|
453
|
+
# "args_schema": PostmanDeleteFolder,
|
454
|
+
# "ref": self.delete_folder
|
455
|
+
# },
|
377
456
|
{
|
378
457
|
"name": "move_folder",
|
379
458
|
"mode": "move_folder",
|
@@ -389,19 +468,75 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
389
468
|
"ref": self.create_request
|
390
469
|
},
|
391
470
|
{
|
392
|
-
"name": "
|
393
|
-
"mode": "
|
394
|
-
"description": "Update
|
395
|
-
"args_schema":
|
396
|
-
"ref": self.
|
471
|
+
"name": "update_request_name",
|
472
|
+
"mode": "update_request_name",
|
473
|
+
"description": "Update request name",
|
474
|
+
"args_schema": PostmanUpdateRequestName,
|
475
|
+
"ref": self.update_request_name
|
476
|
+
},
|
477
|
+
{
|
478
|
+
"name": "update_request_method",
|
479
|
+
"mode": "update_request_method",
|
480
|
+
"description": "Update request HTTP method",
|
481
|
+
"args_schema": PostmanUpdateRequestMethod,
|
482
|
+
"ref": self.update_request_method
|
397
483
|
},
|
398
484
|
{
|
399
|
-
"name": "
|
400
|
-
"mode": "
|
401
|
-
"description": "
|
402
|
-
"args_schema":
|
403
|
-
"ref": self.
|
485
|
+
"name": "update_request_url",
|
486
|
+
"mode": "update_request_url",
|
487
|
+
"description": "Update request URL",
|
488
|
+
"args_schema": PostmanUpdateRequestUrl,
|
489
|
+
"ref": self.update_request_url
|
404
490
|
},
|
491
|
+
{
|
492
|
+
"name": "update_request_description",
|
493
|
+
"mode": "update_request_description",
|
494
|
+
"description": "Update request description",
|
495
|
+
"args_schema": PostmanUpdateRequestDescription,
|
496
|
+
"ref": self.update_request_description
|
497
|
+
},
|
498
|
+
{
|
499
|
+
"name": "update_request_headers",
|
500
|
+
"mode": "update_request_headers",
|
501
|
+
"description": "Update request headers",
|
502
|
+
"args_schema": PostmanUpdateRequestHeaders,
|
503
|
+
"ref": self.update_request_headers
|
504
|
+
},
|
505
|
+
{
|
506
|
+
"name": "update_request_body",
|
507
|
+
"mode": "update_request_body",
|
508
|
+
"description": "Update request body",
|
509
|
+
"args_schema": PostmanUpdateRequestBody,
|
510
|
+
"ref": self.update_request_body
|
511
|
+
},
|
512
|
+
{
|
513
|
+
"name": "update_request_auth",
|
514
|
+
"mode": "update_request_auth",
|
515
|
+
"description": "Update request authentication",
|
516
|
+
"args_schema": PostmanUpdateRequestAuth,
|
517
|
+
"ref": self.update_request_auth
|
518
|
+
},
|
519
|
+
{
|
520
|
+
"name": "update_request_tests",
|
521
|
+
"mode": "update_request_tests",
|
522
|
+
"description": "Update request test scripts",
|
523
|
+
"args_schema": PostmanUpdateRequestTests,
|
524
|
+
"ref": self.update_request_tests
|
525
|
+
},
|
526
|
+
{
|
527
|
+
"name": "update_request_pre_script",
|
528
|
+
"mode": "update_request_pre_script",
|
529
|
+
"description": "Update request pre-request scripts",
|
530
|
+
"args_schema": PostmanUpdateRequestPreScript,
|
531
|
+
"ref": self.update_request_pre_script
|
532
|
+
},
|
533
|
+
# {
|
534
|
+
# "name": "delete_request",
|
535
|
+
# "mode": "delete_request",
|
536
|
+
# "description": "Delete an API request permanently",
|
537
|
+
# "args_schema": PostmanDeleteRequest,
|
538
|
+
# "ref": self.delete_request
|
539
|
+
# },
|
405
540
|
{
|
406
541
|
"name": "duplicate_request",
|
407
542
|
"mode": "duplicate_request",
|
@@ -432,25 +567,25 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
432
567
|
logger.error(f"Exception when getting collections: {stacktrace}")
|
433
568
|
raise ToolException(f"Unable to get collections: {str(e)}")
|
434
569
|
|
435
|
-
def get_collection(self,
|
570
|
+
def get_collection(self, **kwargs) -> str:
|
436
571
|
"""Get a specific collection by ID."""
|
437
572
|
try:
|
438
573
|
response = self._make_request(
|
439
|
-
'GET', f'/collections/{collection_id}')
|
574
|
+
'GET', f'/collections/{self.collection_id}')
|
440
575
|
return json.dumps(response, indent=2)
|
441
576
|
except Exception as e:
|
442
577
|
stacktrace = format_exc()
|
443
578
|
logger.error(
|
444
|
-
f"Exception when getting collection {collection_id}: {stacktrace}")
|
579
|
+
f"Exception when getting collection {self.collection_id}: {stacktrace}")
|
445
580
|
raise ToolException(
|
446
|
-
f"Unable to get collection {collection_id}: {str(e)}")
|
581
|
+
f"Unable to get collection {self.collection_id}: {str(e)}")
|
447
582
|
|
448
|
-
def get_folder(self,
|
583
|
+
def get_folder(self, folder_path: str, **kwargs) -> str:
|
449
584
|
"""Get folders from a collection by path."""
|
450
585
|
try:
|
451
586
|
collection = self._make_request(
|
452
|
-
'GET', f'/collections/{collection_id}')
|
453
|
-
folders = self.
|
587
|
+
'GET', f'/collections/{self.collection_id}')
|
588
|
+
folders = self.analyzer.find_folders_by_path(
|
454
589
|
collection['collection']['item'], folder_path)
|
455
590
|
return json.dumps(folders, indent=2)
|
456
591
|
except Exception as e:
|
@@ -458,21 +593,21 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
458
593
|
logger.error(
|
459
594
|
f"Exception when getting folder {folder_path}: {stacktrace}")
|
460
595
|
raise ToolException(
|
461
|
-
f"Unable to get folder {folder_path} from collection {collection_id}: {str(e)}")
|
596
|
+
f"Unable to get folder {folder_path} from collection {self.collection_id}: {str(e)}")
|
462
597
|
|
463
|
-
def get_folder_requests(self,
|
598
|
+
def get_folder_requests(self, folder_path: str, include_details: bool = False, **kwargs) -> str:
|
464
599
|
"""Get detailed information about all requests in a folder."""
|
465
600
|
try:
|
466
601
|
collection = self._make_request(
|
467
|
-
'GET', f'/collections/{collection_id}')
|
468
|
-
folders = self.
|
602
|
+
'GET', f'/collections/{self.collection_id}')
|
603
|
+
folders = self.analyzer.find_folders_by_path(
|
469
604
|
collection['collection']['item'], folder_path)
|
470
605
|
|
471
606
|
if not folders:
|
472
|
-
raise ToolException(f"Folder '{folder_path}' not found in collection '{collection_id}'.")
|
607
|
+
raise ToolException(f"Folder '{folder_path}' not found in collection '{self.collection_id}'.")
|
473
608
|
|
474
609
|
folder = folders[0]
|
475
|
-
requests = self.
|
610
|
+
requests = self.analyzer.extract_requests_from_items(
|
476
611
|
folder.get('item', []), include_details)
|
477
612
|
|
478
613
|
result = {
|
@@ -490,16 +625,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
490
625
|
raise ToolException(
|
491
626
|
f"Unable to get requests from folder {folder_path}: {str(e)}")
|
492
627
|
|
493
|
-
def search_requests(self,
|
628
|
+
def search_requests(self, query: str, search_in: str = "all", method: str = None, **kwargs) -> str:
|
494
629
|
"""Search for requests across the collection."""
|
495
630
|
try:
|
496
631
|
collection = self._make_request(
|
497
|
-
'GET', f'/collections/{collection_id}')
|
498
|
-
requests = self.
|
632
|
+
'GET', f'/collections/{self.collection_id}')
|
633
|
+
requests = self.analyzer.search_requests_in_items(
|
499
634
|
collection['collection']['item'], query, search_in, method)
|
500
635
|
|
501
636
|
result = {
|
502
|
-
"collection_id": collection_id,
|
637
|
+
"collection_id": self.collection_id,
|
503
638
|
"query": query,
|
504
639
|
"search_in": search_in,
|
505
640
|
"method_filter": method,
|
@@ -512,27 +647,33 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
512
647
|
stacktrace = format_exc()
|
513
648
|
logger.error(f"Exception when searching requests: {stacktrace}")
|
514
649
|
raise ToolException(
|
515
|
-
f"Unable to search requests in collection {collection_id}: {str(e)}")
|
650
|
+
f"Unable to search requests in collection {self.collection_id}: {str(e)}")
|
516
651
|
|
517
|
-
def analyze_collection(self,
|
652
|
+
def analyze_collection(self, include_improvements: bool = False, **kwargs) -> str:
|
518
653
|
"""Analyze a collection for API quality and best practices."""
|
519
654
|
try:
|
520
655
|
collection = self._make_request(
|
521
|
-
'GET', f'/collections/{collection_id}')
|
522
|
-
analysis = self.
|
656
|
+
'GET', f'/collections/{self.collection_id}')
|
657
|
+
analysis = self.analyzer.perform_collection_analysis(collection)
|
658
|
+
|
659
|
+
if include_improvements:
|
660
|
+
improvements = self.analyzer.generate_improvements(analysis)
|
661
|
+
analysis["improvements"] = improvements
|
662
|
+
analysis["improvement_count"] = len(improvements)
|
663
|
+
|
523
664
|
return json.dumps(analysis, indent=2)
|
524
665
|
except Exception as e:
|
525
666
|
stacktrace = format_exc()
|
526
667
|
logger.error(f"Exception when analyzing collection: {stacktrace}")
|
527
668
|
raise ToolException(
|
528
|
-
f"Unable to analyze collection {collection_id}: {str(e)}")
|
669
|
+
f"Unable to analyze collection {self.collection_id}: {str(e)}")
|
529
670
|
|
530
|
-
def analyze_folder(self,
|
671
|
+
def analyze_folder(self, folder_path: str, include_improvements: bool = False, **kwargs) -> str:
|
531
672
|
"""Analyze a specific folder within a collection."""
|
532
673
|
try:
|
533
674
|
collection = self._make_request(
|
534
|
-
'GET', f'/collections/{collection_id}')
|
535
|
-
folders = self.
|
675
|
+
'GET', f'/collections/{self.collection_id}')
|
676
|
+
folders = self.analyzer.find_folders_by_path(
|
536
677
|
collection['collection']['item'], folder_path)
|
537
678
|
|
538
679
|
if not folders:
|
@@ -540,7 +681,13 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
540
681
|
|
541
682
|
folder_analyses = []
|
542
683
|
for folder in folders:
|
543
|
-
analysis = self.
|
684
|
+
analysis = self.analyzer.perform_folder_analysis(folder, folder_path)
|
685
|
+
|
686
|
+
if include_improvements:
|
687
|
+
improvements = self.analyzer.generate_folder_improvements(analysis)
|
688
|
+
analysis["improvements"] = improvements
|
689
|
+
analysis["improvement_count"] = len(improvements)
|
690
|
+
|
544
691
|
folder_analyses.append(analysis)
|
545
692
|
|
546
693
|
return json.dumps(folder_analyses, indent=2)
|
@@ -550,28 +697,35 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
550
697
|
raise ToolException(
|
551
698
|
f"Unable to analyze folder {folder_path}: {str(e)}")
|
552
699
|
|
553
|
-
def
|
554
|
-
"""
|
700
|
+
def analyze_request(self, request_path: str, include_improvements: bool = False, **kwargs) -> str:
|
701
|
+
"""Analyze a specific request within a collection."""
|
555
702
|
try:
|
556
703
|
collection = self._make_request(
|
557
|
-
'GET', f'/collections/{collection_id}')
|
558
|
-
|
559
|
-
improvements = self._generate_improvements(analysis)
|
704
|
+
'GET', f'/collections/{self.collection_id}')
|
705
|
+
collection_data = collection["collection"]
|
560
706
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
"
|
566
|
-
}
|
707
|
+
# Find the request
|
708
|
+
request_item = self.analyzer.find_request_by_path(
|
709
|
+
collection_data["item"], request_path)
|
710
|
+
if not request_item:
|
711
|
+
raise ToolException(f"Request '{request_path}' not found")
|
567
712
|
|
568
|
-
|
713
|
+
# Perform request analysis
|
714
|
+
analysis = self.analyzer.perform_request_analysis(request_item)
|
715
|
+
analysis["request_path"] = request_path
|
716
|
+
analysis["collection_id"] = self.collection_id
|
717
|
+
|
718
|
+
if include_improvements:
|
719
|
+
improvements = self.analyzer.generate_request_improvements(analysis)
|
720
|
+
analysis["improvements"] = improvements
|
721
|
+
analysis["improvement_count"] = len(improvements)
|
722
|
+
|
723
|
+
return json.dumps(analysis, indent=2)
|
569
724
|
except Exception as e:
|
570
725
|
stacktrace = format_exc()
|
571
|
-
logger.error(
|
572
|
-
f"Exception when generating improvements: {stacktrace}")
|
726
|
+
logger.error(f"Exception when analyzing request: {stacktrace}")
|
573
727
|
raise ToolException(
|
574
|
-
f"Unable to
|
728
|
+
f"Unable to analyze request {request_path}: {str(e)}")
|
575
729
|
|
576
730
|
# =================================================================
|
577
731
|
# COLLECTION MANAGEMENT METHODS
|
@@ -608,52 +762,104 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
608
762
|
raise ToolException(
|
609
763
|
f"Unable to create collection '{name}': {str(e)}")
|
610
764
|
|
611
|
-
def
|
612
|
-
|
613
|
-
"""Update an existing collection."""
|
765
|
+
def update_collection_name(self, name: str, **kwargs) -> str:
|
766
|
+
"""Update collection name."""
|
614
767
|
try:
|
615
768
|
# Get current collection
|
616
769
|
current = self._make_request(
|
617
|
-
'GET', f'/collections/{collection_id}')
|
770
|
+
'GET', f'/collections/{self.collection_id}')
|
618
771
|
collection_data = current["collection"]
|
619
772
|
|
620
|
-
# Update
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
773
|
+
# Update name
|
774
|
+
collection_data["info"]["name"] = name
|
775
|
+
|
776
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
777
|
+
json={"collection": collection_data})
|
778
|
+
return json.dumps(response, indent=2)
|
779
|
+
except Exception as e:
|
780
|
+
stacktrace = format_exc()
|
781
|
+
logger.error(f"Exception when updating collection name: {stacktrace}")
|
782
|
+
raise ToolException(
|
783
|
+
f"Unable to update collection {self.collection_id} name: {str(e)}")
|
784
|
+
|
785
|
+
def update_collection_description(self, description: str, **kwargs) -> str:
|
786
|
+
"""Update collection description."""
|
787
|
+
try:
|
788
|
+
# Get current collection
|
789
|
+
current = self._make_request(
|
790
|
+
'GET', f'/collections/{self.collection_id}')
|
791
|
+
collection_data = current["collection"]
|
792
|
+
|
793
|
+
# Update description
|
794
|
+
collection_data["info"]["description"] = description
|
795
|
+
|
796
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
797
|
+
json={"collection": collection_data})
|
798
|
+
return json.dumps(response, indent=2)
|
799
|
+
except Exception as e:
|
800
|
+
stacktrace = format_exc()
|
801
|
+
logger.error(f"Exception when updating collection description: {stacktrace}")
|
802
|
+
raise ToolException(
|
803
|
+
f"Unable to update collection {self.collection_id} description: {str(e)}")
|
804
|
+
|
805
|
+
def update_collection_variables(self, variables: List[Dict], **kwargs) -> str:
|
806
|
+
"""Update collection variables."""
|
807
|
+
try:
|
808
|
+
# Get current collection
|
809
|
+
current = self._make_request(
|
810
|
+
'GET', f'/collections/{self.collection_id}')
|
811
|
+
collection_data = current["collection"]
|
812
|
+
|
813
|
+
# Update variables
|
814
|
+
collection_data["variable"] = variables
|
815
|
+
|
816
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
817
|
+
json={"collection": collection_data})
|
818
|
+
return json.dumps(response, indent=2)
|
819
|
+
except Exception as e:
|
820
|
+
stacktrace = format_exc()
|
821
|
+
logger.error(f"Exception when updating collection variables: {stacktrace}")
|
822
|
+
raise ToolException(
|
823
|
+
f"Unable to update collection {self.collection_id} variables: {str(e)}")
|
629
824
|
|
630
|
-
|
825
|
+
def update_collection_auth(self, auth: Dict, **kwargs) -> str:
|
826
|
+
"""Update collection authentication settings."""
|
827
|
+
try:
|
828
|
+
# Get current collection
|
829
|
+
current = self._make_request(
|
830
|
+
'GET', f'/collections/{self.collection_id}')
|
831
|
+
collection_data = current["collection"]
|
832
|
+
|
833
|
+
# Update auth
|
834
|
+
collection_data["auth"] = auth
|
835
|
+
|
836
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
631
837
|
json={"collection": collection_data})
|
632
838
|
return json.dumps(response, indent=2)
|
633
839
|
except Exception as e:
|
634
840
|
stacktrace = format_exc()
|
635
|
-
logger.error(f"Exception when updating collection: {stacktrace}")
|
841
|
+
logger.error(f"Exception when updating collection auth: {stacktrace}")
|
636
842
|
raise ToolException(
|
637
|
-
f"Unable to update collection {collection_id}: {str(e)}")
|
843
|
+
f"Unable to update collection {self.collection_id} auth: {str(e)}")
|
638
844
|
|
639
|
-
def delete_collection(self,
|
845
|
+
def delete_collection(self, **kwargs) -> str:
|
640
846
|
"""Delete a collection permanently."""
|
641
847
|
try:
|
642
848
|
response = self._make_request(
|
643
|
-
'DELETE', f'/collections/{collection_id}')
|
644
|
-
return json.dumps({"message": f"Collection {collection_id} deleted successfully"}, indent=2)
|
849
|
+
'DELETE', f'/collections/{self.collection_id}')
|
850
|
+
return json.dumps({"message": f"Collection {self.collection_id} deleted successfully"}, indent=2)
|
645
851
|
except Exception as e:
|
646
852
|
stacktrace = format_exc()
|
647
853
|
logger.error(f"Exception when deleting collection: {stacktrace}")
|
648
854
|
raise ToolException(
|
649
|
-
f"Unable to delete collection {collection_id}: {str(e)}")
|
855
|
+
f"Unable to delete collection {self.collection_id}: {str(e)}")
|
650
856
|
|
651
|
-
def duplicate_collection(self,
|
857
|
+
def duplicate_collection(self, new_name: str, **kwargs) -> str:
|
652
858
|
"""Create a copy of an existing collection."""
|
653
859
|
try:
|
654
860
|
# Get the original collection
|
655
861
|
original = self._make_request(
|
656
|
-
'GET', f'/collections/{collection_id}')
|
862
|
+
'GET', f'/collections/{self.collection_id}')
|
657
863
|
collection_data = original["collection"]
|
658
864
|
|
659
865
|
# Update the name and remove IDs to create a new collection
|
@@ -662,7 +868,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
662
868
|
del collection_data["info"]["_postman_id"]
|
663
869
|
|
664
870
|
# Remove item IDs recursively
|
665
|
-
self.
|
871
|
+
self.analyzer.remove_item_ids(collection_data.get("item", []))
|
666
872
|
|
667
873
|
response = self._make_request(
|
668
874
|
'POST', '/collections', json={"collection": collection_data})
|
@@ -672,19 +878,19 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
672
878
|
logger.error(
|
673
879
|
f"Exception when duplicating collection: {stacktrace}")
|
674
880
|
raise ToolException(
|
675
|
-
f"Unable to duplicate collection {collection_id}: {str(e)}")
|
881
|
+
f"Unable to duplicate collection {self.collection_id}: {str(e)}")
|
676
882
|
|
677
883
|
# =================================================================
|
678
884
|
# FOLDER MANAGEMENT METHODS
|
679
885
|
# =================================================================
|
680
886
|
|
681
|
-
def create_folder(self,
|
887
|
+
def create_folder(self, name: str, description: str = None,
|
682
888
|
parent_path: str = None, auth: Dict = None, **kwargs) -> str:
|
683
889
|
"""Create a new folder in a collection."""
|
684
890
|
try:
|
685
891
|
# Get current collection
|
686
892
|
collection = self._make_request(
|
687
|
-
'GET', f'/collections/{collection_id}')
|
893
|
+
'GET', f'/collections/{self.collection_id}')
|
688
894
|
collection_data = collection["collection"]
|
689
895
|
|
690
896
|
# Create folder item
|
@@ -700,7 +906,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
700
906
|
|
701
907
|
# Add folder to appropriate location
|
702
908
|
if parent_path:
|
703
|
-
parent_folders = self.
|
909
|
+
parent_folders = self.analyzer.find_folders_by_path(
|
704
910
|
collection_data["item"], parent_path)
|
705
911
|
if not parent_folders:
|
706
912
|
raise ToolException(
|
@@ -710,7 +916,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
710
916
|
collection_data["item"].append(folder_item)
|
711
917
|
|
712
918
|
# Update collection
|
713
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
919
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
714
920
|
json={"collection": collection_data})
|
715
921
|
return json.dumps({"message": f"Folder '{name}' created successfully"}, indent=2)
|
716
922
|
except Exception as e:
|
@@ -718,53 +924,78 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
718
924
|
logger.error(f"Exception when creating folder: {stacktrace}")
|
719
925
|
raise ToolException(f"Unable to create folder '{name}': {str(e)}")
|
720
926
|
|
721
|
-
def
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
collection = self._make_request(
|
727
|
-
'GET', f'/collections/{collection_id}')
|
728
|
-
collection_data = collection["collection"]
|
927
|
+
def _get_folder_id(self, folder_path: str) -> str:
|
928
|
+
"""Helper method to get folder ID by path."""
|
929
|
+
collection = self._make_request(
|
930
|
+
'GET', f'/collections/{self.collection_id}')
|
931
|
+
collection_data = collection["collection"]
|
729
932
|
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
933
|
+
# Find the folder
|
934
|
+
folders = self.analyzer.find_folders_by_path(
|
935
|
+
collection_data["item"], folder_path)
|
936
|
+
if not folders:
|
937
|
+
raise ToolException(f"Folder '{folder_path}' not found")
|
735
938
|
|
736
|
-
|
939
|
+
folder = folders[0]
|
940
|
+
|
941
|
+
# Get the folder ID
|
942
|
+
folder_id = folder.get("id")
|
943
|
+
if not folder_id:
|
944
|
+
# If ID is not available directly, try to use the item ID
|
945
|
+
if "_postman_id" in folder:
|
946
|
+
folder_id = folder["_postman_id"]
|
947
|
+
else:
|
948
|
+
raise ToolException(f"Folder ID not found for '{folder_path}'")
|
949
|
+
|
950
|
+
return folder_id
|
737
951
|
|
738
|
-
|
952
|
+
def update_folder(self, folder_path: str, name: str = None,
|
953
|
+
description: str = None, auth: Dict = None, **kwargs) -> str:
|
954
|
+
"""Update folder properties using the direct folder endpoint."""
|
955
|
+
try:
|
956
|
+
# Get the folder ID
|
957
|
+
folder_id = self._get_folder_id(folder_path)
|
958
|
+
|
959
|
+
# Create update payload
|
960
|
+
folder_update = {}
|
739
961
|
if name:
|
740
|
-
|
962
|
+
folder_update["name"] = name
|
741
963
|
if description is not None:
|
742
|
-
|
964
|
+
folder_update["description"] = description
|
743
965
|
if auth is not None:
|
744
|
-
|
745
|
-
|
746
|
-
#
|
747
|
-
|
748
|
-
|
749
|
-
|
966
|
+
folder_update["auth"] = auth
|
967
|
+
|
968
|
+
# Only update if we have properties to change
|
969
|
+
if folder_update:
|
970
|
+
# Update folder using the direct API endpoint
|
971
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/folders/{folder_id}',
|
972
|
+
json=folder_update)
|
973
|
+
return json.dumps({"success": True, "message": f"Folder '{folder_path}' updated successfully"}, indent=2)
|
974
|
+
else:
|
975
|
+
return json.dumps({"success": True, "message": f"No changes requested for folder '{folder_path}'"}, indent=2)
|
976
|
+
except Exception as e:
|
977
|
+
stacktrace = format_exc()
|
978
|
+
logger.error(f"Exception when updating folder: {stacktrace}")
|
979
|
+
raise ToolException(
|
980
|
+
f"Unable to update folder '{folder_path}': {str(e)}")
|
750
981
|
except Exception as e:
|
751
982
|
stacktrace = format_exc()
|
752
983
|
logger.error(f"Exception when updating folder: {stacktrace}")
|
753
984
|
raise ToolException(
|
754
985
|
f"Unable to update folder '{folder_path}': {str(e)}")
|
755
986
|
|
756
|
-
def delete_folder(self,
|
987
|
+
def delete_folder(self, folder_path: str, **kwargs) -> str:
|
757
988
|
"""Delete a folder and all its contents permanently."""
|
758
989
|
try:
|
759
990
|
# Get current collection
|
760
991
|
collection = self._make_request(
|
761
|
-
'GET', f'/collections/{collection_id}')
|
992
|
+
'GET', f'/collections/{self.collection_id}')
|
762
993
|
collection_data = collection["collection"]
|
763
994
|
|
764
995
|
# Find and remove the folder
|
765
|
-
if self.
|
996
|
+
if self.analyzer.remove_folder_by_path(collection_data["item"], folder_path):
|
766
997
|
# Update collection
|
767
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
998
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
768
999
|
json={"collection": collection_data})
|
769
1000
|
return json.dumps({"message": f"Folder '{folder_path}' deleted successfully"}, indent=2)
|
770
1001
|
else:
|
@@ -775,16 +1006,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
775
1006
|
raise ToolException(
|
776
1007
|
f"Unable to delete folder '{folder_path}': {str(e)}")
|
777
1008
|
|
778
|
-
def move_folder(self,
|
1009
|
+
def move_folder(self, source_path: str, target_path: str = None, **kwargs) -> str:
|
779
1010
|
"""Move a folder to a different location within the collection."""
|
780
1011
|
try:
|
781
1012
|
# Get current collection
|
782
1013
|
collection = self._make_request(
|
783
|
-
'GET', f'/collections/{collection_id}')
|
1014
|
+
'GET', f'/collections/{self.collection_id}')
|
784
1015
|
collection_data = collection["collection"]
|
785
1016
|
|
786
1017
|
# Find source folder
|
787
|
-
source_folder = self.
|
1018
|
+
source_folder = self.analyzer.find_folders_by_path(
|
788
1019
|
collection_data["item"], source_path)
|
789
1020
|
if not source_folder:
|
790
1021
|
raise ToolException(f"Source folder '{source_path}' not found")
|
@@ -792,11 +1023,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
792
1023
|
folder_data = source_folder[0].copy()
|
793
1024
|
|
794
1025
|
# Remove from source location
|
795
|
-
self.
|
1026
|
+
self.analyzer.remove_folder_by_path(collection_data["item"], source_path)
|
796
1027
|
|
797
1028
|
# Add to target location
|
798
1029
|
if target_path:
|
799
|
-
target_folders = self.
|
1030
|
+
target_folders = self.analyzer.find_folders_by_path(
|
800
1031
|
collection_data["item"], target_path)
|
801
1032
|
if not target_folders:
|
802
1033
|
raise ToolException(
|
@@ -806,7 +1037,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
806
1037
|
collection_data["item"].append(folder_data)
|
807
1038
|
|
808
1039
|
# Update collection
|
809
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
1040
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
810
1041
|
json={"collection": collection_data})
|
811
1042
|
return json.dumps({"message": f"Folder moved from '{source_path}' to '{target_path or 'root'}'"}, indent=2)
|
812
1043
|
except Exception as e:
|
@@ -819,7 +1050,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
819
1050
|
# REQUEST MANAGEMENT METHODS
|
820
1051
|
# =================================================================
|
821
1052
|
|
822
|
-
def create_request(self,
|
1053
|
+
def create_request(self, name: str, method: str, url: str,
|
823
1054
|
folder_path: str = None, description: str = None, headers: List[Dict] = None,
|
824
1055
|
body: Dict = None, auth: Dict = None, tests: str = None,
|
825
1056
|
pre_request_script: str = None, **kwargs) -> str:
|
@@ -827,7 +1058,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
827
1058
|
try:
|
828
1059
|
# Get current collection
|
829
1060
|
collection = self._make_request(
|
830
|
-
'GET', f'/collections/{collection_id}')
|
1061
|
+
'GET', f'/collections/{self.collection_id}')
|
831
1062
|
collection_data = collection["collection"]
|
832
1063
|
|
833
1064
|
# Create request item
|
@@ -870,7 +1101,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
870
1101
|
|
871
1102
|
# Add request to appropriate location
|
872
1103
|
if folder_path:
|
873
|
-
folders = self.
|
1104
|
+
folders = self.analyzer.find_folders_by_path(
|
874
1105
|
collection_data["item"], folder_path)
|
875
1106
|
if not folders:
|
876
1107
|
raise ToolException(f"Folder '{folder_path}' not found")
|
@@ -879,7 +1110,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
879
1110
|
collection_data["item"].append(request_item)
|
880
1111
|
|
881
1112
|
# Update collection
|
882
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
1113
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
883
1114
|
json={"collection": collection_data})
|
884
1115
|
return json.dumps({"message": f"Request '{name}' created successfully"}, indent=2)
|
885
1116
|
except Exception as e:
|
@@ -887,92 +1118,263 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
887
1118
|
logger.error(f"Exception when creating request: {stacktrace}")
|
888
1119
|
raise ToolException(f"Unable to create request '{name}': {str(e)}")
|
889
1120
|
|
890
|
-
def
|
891
|
-
|
892
|
-
headers: List[Dict] = None, body: Dict = None, auth: Dict = None,
|
893
|
-
tests: str = None, pre_request_script: str = None, **kwargs) -> str:
|
894
|
-
"""Update an existing API request."""
|
1121
|
+
def update_request_name(self, request_path: str, name: str, **kwargs) -> str:
|
1122
|
+
"""Update request name."""
|
895
1123
|
try:
|
896
|
-
# Get
|
897
|
-
|
898
|
-
|
899
|
-
|
1124
|
+
# Get request item and ID
|
1125
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1126
|
+
|
1127
|
+
# Create update payload
|
1128
|
+
request_update = {
|
1129
|
+
"name": name
|
1130
|
+
}
|
900
1131
|
|
901
|
-
#
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
1132
|
+
# Update the name field
|
1133
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1134
|
+
json=request_update)
|
1135
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' name updated successfully"}, indent=2)
|
1136
|
+
except Exception as e:
|
1137
|
+
stacktrace = format_exc()
|
1138
|
+
logger.error(f"Exception when updating request name: {stacktrace}")
|
1139
|
+
raise ToolException(
|
1140
|
+
f"Unable to update request '{request_path}' name: {str(e)}")
|
906
1141
|
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
request_item["request"]["header"] = headers
|
918
|
-
if body is not None:
|
919
|
-
request_item["request"]["body"] = body
|
920
|
-
if auth is not None:
|
921
|
-
request_item["request"]["auth"] = auth
|
1142
|
+
def update_request_method(self, request_path: str, method: str, **kwargs) -> str:
|
1143
|
+
"""Update request HTTP method."""
|
1144
|
+
try:
|
1145
|
+
# Get request item and ID
|
1146
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1147
|
+
|
1148
|
+
# Create update payload
|
1149
|
+
request_update = {
|
1150
|
+
"method": method.upper()
|
1151
|
+
}
|
922
1152
|
|
923
|
-
# Update
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
events.append({
|
933
|
-
"listen": "prerequest",
|
934
|
-
"script": {
|
935
|
-
"exec": pre_request_script.split('\n'),
|
936
|
-
"type": "text/javascript"
|
937
|
-
}
|
938
|
-
})
|
939
|
-
|
940
|
-
if tests is not None:
|
941
|
-
# Remove existing test events
|
942
|
-
events = [e for e in events if e.get("listen") != "test"]
|
943
|
-
if tests:
|
944
|
-
events.append({
|
945
|
-
"listen": "test",
|
946
|
-
"script": {
|
947
|
-
"exec": tests.split('\n'),
|
948
|
-
"type": "text/javascript"
|
949
|
-
}
|
950
|
-
})
|
1153
|
+
# Update the method field
|
1154
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1155
|
+
json=request_update)
|
1156
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' method updated successfully"}, indent=2)
|
1157
|
+
except Exception as e:
|
1158
|
+
stacktrace = format_exc()
|
1159
|
+
logger.error(f"Exception when updating request method: {stacktrace}")
|
1160
|
+
raise ToolException(
|
1161
|
+
f"Unable to update request '{request_path}' method: {str(e)}")
|
951
1162
|
|
952
|
-
|
1163
|
+
def update_request_url(self, request_path: str, url: str, **kwargs) -> str:
|
1164
|
+
"""Update request URL."""
|
1165
|
+
try:
|
1166
|
+
# Get request item and ID
|
1167
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1168
|
+
|
1169
|
+
# Create update payload
|
1170
|
+
request_update = {
|
1171
|
+
"url": url
|
1172
|
+
}
|
953
1173
|
|
954
|
-
# Update
|
955
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
956
|
-
json=
|
957
|
-
return json.dumps({"message": f"Request '{request_path}' updated successfully"}, indent=2)
|
1174
|
+
# Update the URL field
|
1175
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1176
|
+
json=request_update)
|
1177
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' URL updated successfully"}, indent=2)
|
1178
|
+
except Exception as e:
|
1179
|
+
stacktrace = format_exc()
|
1180
|
+
logger.error(f"Exception when updating request URL: {stacktrace}")
|
1181
|
+
raise ToolException(
|
1182
|
+
f"Unable to update request '{request_path}' URL: {str(e)}")
|
1183
|
+
|
1184
|
+
def _get_request_item_and_id(self, request_path: str) -> Tuple[Dict, str, Dict]:
|
1185
|
+
"""Helper method to get request item and ID by path. Returns (request_item, request_id, collection_data)."""
|
1186
|
+
collection = self._make_request(
|
1187
|
+
'GET', f'/collections/{self.collection_id}')
|
1188
|
+
collection_data = collection["collection"]
|
1189
|
+
|
1190
|
+
# Find the request
|
1191
|
+
request_item = self.analyzer.find_request_by_path(
|
1192
|
+
collection_data["item"], request_path)
|
1193
|
+
if not request_item:
|
1194
|
+
raise ToolException(f"Request '{request_path}' not found")
|
1195
|
+
|
1196
|
+
# Get the request ID
|
1197
|
+
request_id = request_item.get("id")
|
1198
|
+
if not request_id:
|
1199
|
+
# If ID is not available directly, try to use the full item ID path
|
1200
|
+
if "_postman_id" in request_item:
|
1201
|
+
request_id = request_item["_postman_id"]
|
1202
|
+
else:
|
1203
|
+
raise ToolException(f"Request ID not found for '{request_path}'")
|
1204
|
+
|
1205
|
+
return request_item, request_id, collection_data
|
1206
|
+
|
1207
|
+
def update_request_description(self, request_path: str, description: str, **kwargs) -> str:
|
1208
|
+
"""Update request description."""
|
1209
|
+
try:
|
1210
|
+
# Get request item and ID
|
1211
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1212
|
+
|
1213
|
+
# For description update, we need to properly format the payload
|
1214
|
+
# according to Postman API requirements
|
1215
|
+
request_update = {
|
1216
|
+
"description": description
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
# Update only the description field
|
1220
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1221
|
+
json=request_update)
|
1222
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' description updated successfully"}, indent=2)
|
1223
|
+
except Exception as e:
|
1224
|
+
stacktrace = format_exc()
|
1225
|
+
logger.error(f"Exception when updating request description: {stacktrace}")
|
1226
|
+
raise ToolException(
|
1227
|
+
f"Unable to update request '{request_path}' description: {str(e)}")
|
1228
|
+
|
1229
|
+
def update_request_headers(self, request_path: str, headers: List[Dict], **kwargs) -> str:
|
1230
|
+
"""Update request headers."""
|
1231
|
+
try:
|
1232
|
+
# Get request item and ID
|
1233
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1234
|
+
|
1235
|
+
# Create update payload
|
1236
|
+
request_update = {
|
1237
|
+
"header": headers
|
1238
|
+
}
|
1239
|
+
|
1240
|
+
# Update the headers field
|
1241
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1242
|
+
json=request_update)
|
1243
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' headers updated successfully"}, indent=2)
|
1244
|
+
except Exception as e:
|
1245
|
+
stacktrace = format_exc()
|
1246
|
+
logger.error(f"Exception when updating request headers: {stacktrace}")
|
1247
|
+
raise ToolException(
|
1248
|
+
f"Unable to update request '{request_path}' headers: {str(e)}")
|
1249
|
+
|
1250
|
+
def update_request_body(self, request_path: str, body: Dict, **kwargs) -> str:
|
1251
|
+
"""Update request body."""
|
1252
|
+
try:
|
1253
|
+
# Get request item and ID
|
1254
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1255
|
+
|
1256
|
+
# Create update payload
|
1257
|
+
request_update = {
|
1258
|
+
"body": body
|
1259
|
+
}
|
1260
|
+
|
1261
|
+
# Update the body field
|
1262
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1263
|
+
json=request_update)
|
1264
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' body updated successfully"}, indent=2)
|
1265
|
+
except Exception as e:
|
1266
|
+
stacktrace = format_exc()
|
1267
|
+
logger.error(f"Exception when updating request body: {stacktrace}")
|
1268
|
+
raise ToolException(
|
1269
|
+
f"Unable to update request '{request_path}' body: {str(e)}")
|
1270
|
+
|
1271
|
+
def update_request_auth(self, request_path: str, auth: Dict, **kwargs) -> str:
|
1272
|
+
"""Update request authentication."""
|
1273
|
+
try:
|
1274
|
+
# Get request item and ID
|
1275
|
+
request_item, request_id, _ = self._get_request_item_and_id(request_path)
|
1276
|
+
|
1277
|
+
# Create update payload
|
1278
|
+
request_update = {
|
1279
|
+
"auth": auth
|
1280
|
+
}
|
1281
|
+
|
1282
|
+
# Update the auth field
|
1283
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1284
|
+
json=request_update)
|
1285
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' auth updated successfully"}, indent=2)
|
1286
|
+
except Exception as e:
|
1287
|
+
stacktrace = format_exc()
|
1288
|
+
logger.error(f"Exception when updating request auth: {stacktrace}")
|
1289
|
+
raise ToolException(
|
1290
|
+
f"Unable to update request '{request_path}' auth: {str(e)}")
|
1291
|
+
|
1292
|
+
def update_request_tests(self, request_path: str, tests: str, **kwargs) -> str:
|
1293
|
+
"""Update request test scripts."""
|
1294
|
+
try:
|
1295
|
+
# Get the request ID
|
1296
|
+
_, request_id, _ = self._get_request_item_and_id(request_path)
|
1297
|
+
|
1298
|
+
# Get current request to preserve existing data
|
1299
|
+
current_request = self._make_request('GET', f'/collections/{self.collection_id}/requests/{request_id}')
|
1300
|
+
request_data = current_request.get("data", {})
|
1301
|
+
|
1302
|
+
# Prepare the events array - preserve any non-test events
|
1303
|
+
events = [event for event in request_data.get("events", []) if event.get("listen") != "test"]
|
1304
|
+
|
1305
|
+
# Add the new test script
|
1306
|
+
events.append({
|
1307
|
+
"listen": "test",
|
1308
|
+
"script": {
|
1309
|
+
"type": "text/javascript",
|
1310
|
+
"exec": tests.strip().split('\n')
|
1311
|
+
}
|
1312
|
+
})
|
1313
|
+
|
1314
|
+
# Update the events array in the request data
|
1315
|
+
request_data["events"] = events
|
1316
|
+
|
1317
|
+
# Update the request using the individual request endpoint
|
1318
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1319
|
+
json=request_data)
|
1320
|
+
|
1321
|
+
logger.info(f"Test script updated successfully for request '{request_path}'")
|
1322
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' tests updated successfully"}, indent=2)
|
1323
|
+
except Exception as e:
|
1324
|
+
stacktrace = format_exc()
|
1325
|
+
logger.error(f"Exception when updating request tests: {stacktrace}")
|
1326
|
+
raise ToolException(
|
1327
|
+
f"Unable to update request '{request_path}' tests: {str(e)}")
|
1328
|
+
|
1329
|
+
def update_request_pre_script(self, request_path: str, pre_request_script: str, **kwargs) -> str:
|
1330
|
+
"""Update request pre-request scripts."""
|
1331
|
+
try:
|
1332
|
+
# Get the request ID
|
1333
|
+
_, request_id, _ = self._get_request_item_and_id(request_path)
|
1334
|
+
|
1335
|
+
# Get current request to preserve existing data
|
1336
|
+
current_request = self._make_request('GET', f'/collections/{self.collection_id}/requests/{request_id}')
|
1337
|
+
request_data = current_request.get("data", {})
|
1338
|
+
|
1339
|
+
# Prepare the events array - preserve any non-prerequest events
|
1340
|
+
events = [event for event in request_data.get("events", []) if event.get("listen") != "prerequest"]
|
1341
|
+
|
1342
|
+
# Add the new prerequest script
|
1343
|
+
events.append({
|
1344
|
+
"listen": "prerequest",
|
1345
|
+
"script": {
|
1346
|
+
"type": "text/javascript",
|
1347
|
+
"exec": pre_request_script.strip().split('\n')
|
1348
|
+
}
|
1349
|
+
})
|
1350
|
+
|
1351
|
+
# Update the events array in the request data
|
1352
|
+
request_data["events"] = events
|
1353
|
+
|
1354
|
+
# Update the request using the individual request endpoint
|
1355
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}/requests/{request_id}',
|
1356
|
+
json=request_data)
|
1357
|
+
|
1358
|
+
logger.info(f"Pre-request script updated successfully for request '{request_path}'")
|
1359
|
+
return json.dumps({"success": True, "message": f"Request '{request_path}' pre-script updated successfully"}, indent=2)
|
958
1360
|
except Exception as e:
|
959
1361
|
stacktrace = format_exc()
|
960
|
-
logger.error(f"Exception when updating request: {stacktrace}")
|
1362
|
+
logger.error(f"Exception when updating request pre-script: {stacktrace}")
|
961
1363
|
raise ToolException(
|
962
|
-
f"Unable to update request '{request_path}': {str(e)}")
|
1364
|
+
f"Unable to update request '{request_path}' pre-script: {str(e)}")
|
963
1365
|
|
964
|
-
def delete_request(self,
|
1366
|
+
def delete_request(self, request_path: str, **kwargs) -> str:
|
965
1367
|
"""Delete an API request permanently."""
|
966
1368
|
try:
|
967
1369
|
# Get current collection
|
968
1370
|
collection = self._make_request(
|
969
|
-
'GET', f'/collections/{collection_id}')
|
1371
|
+
'GET', f'/collections/{self.collection_id}')
|
970
1372
|
collection_data = collection["collection"]
|
971
1373
|
|
972
1374
|
# Find and remove the request
|
973
|
-
if self.
|
1375
|
+
if self.analyzer.remove_request_by_path(collection_data["item"], request_path):
|
974
1376
|
# Update collection
|
975
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
1377
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
976
1378
|
json={"collection": collection_data})
|
977
1379
|
return json.dumps({"message": f"Request '{request_path}' deleted successfully"}, indent=2)
|
978
1380
|
else:
|
@@ -983,17 +1385,17 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
983
1385
|
raise ToolException(
|
984
1386
|
f"Unable to delete request '{request_path}': {str(e)}")
|
985
1387
|
|
986
|
-
def duplicate_request(self,
|
1388
|
+
def duplicate_request(self, source_path: str, new_name: str,
|
987
1389
|
target_path: str = None, **kwargs) -> str:
|
988
1390
|
"""Create a copy of an existing API request."""
|
989
1391
|
try:
|
990
1392
|
# Get current collection
|
991
1393
|
collection = self._make_request(
|
992
|
-
'GET', f'/collections/{collection_id}')
|
1394
|
+
'GET', f'/collections/{self.collection_id}')
|
993
1395
|
collection_data = collection["collection"]
|
994
1396
|
|
995
1397
|
# Find source request
|
996
|
-
source_request = self.
|
1398
|
+
source_request = self.analyzer.find_request_by_path(
|
997
1399
|
collection_data["item"], source_path)
|
998
1400
|
if not source_request:
|
999
1401
|
raise ToolException(
|
@@ -1009,7 +1411,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1009
1411
|
|
1010
1412
|
# Add to target location
|
1011
1413
|
if target_path:
|
1012
|
-
folders = self.
|
1414
|
+
folders = self.analyzer.find_folders_by_path(
|
1013
1415
|
collection_data["item"], target_path)
|
1014
1416
|
if not folders:
|
1015
1417
|
raise ToolException(
|
@@ -1019,14 +1421,14 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1019
1421
|
# Add to same location as source
|
1020
1422
|
source_folder_path = "/".join(source_path.split("/")[:-1])
|
1021
1423
|
if source_folder_path:
|
1022
|
-
folders = self.
|
1424
|
+
folders = self.analyzer.find_folders_by_path(
|
1023
1425
|
collection_data["item"], source_folder_path)
|
1024
1426
|
folders[0]["item"].append(request_copy)
|
1025
1427
|
else:
|
1026
1428
|
collection_data["item"].append(request_copy)
|
1027
1429
|
|
1028
1430
|
# Update collection
|
1029
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
1431
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
1030
1432
|
json={"collection": collection_data})
|
1031
1433
|
return json.dumps({"message": f"Request duplicated as '{new_name}'"}, indent=2)
|
1032
1434
|
except Exception as e:
|
@@ -1035,16 +1437,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1035
1437
|
raise ToolException(
|
1036
1438
|
f"Unable to duplicate request '{source_path}': {str(e)}")
|
1037
1439
|
|
1038
|
-
def move_request(self,
|
1440
|
+
def move_request(self, source_path: str, target_path: str = None, **kwargs) -> str:
|
1039
1441
|
"""Move an API request to a different folder."""
|
1040
1442
|
try:
|
1041
1443
|
# Get current collection
|
1042
1444
|
collection = self._make_request(
|
1043
|
-
'GET', f'/collections/{collection_id}')
|
1445
|
+
'GET', f'/collections/{self.collection_id}')
|
1044
1446
|
collection_data = collection["collection"]
|
1045
1447
|
|
1046
1448
|
# Find source request
|
1047
|
-
source_request = self.
|
1449
|
+
source_request = self.analyzer.find_request_by_path(
|
1048
1450
|
collection_data["item"], source_path)
|
1049
1451
|
if not source_request:
|
1050
1452
|
raise ToolException(
|
@@ -1053,11 +1455,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1053
1455
|
request_data = json.loads(json.dumps(source_request)) # Deep copy
|
1054
1456
|
|
1055
1457
|
# Remove from source location
|
1056
|
-
self.
|
1458
|
+
self.analyzer.remove_request_by_path(collection_data["item"], source_path)
|
1057
1459
|
|
1058
1460
|
# Add to target location
|
1059
1461
|
if target_path:
|
1060
|
-
folders = self.
|
1462
|
+
folders = self.analyzer.find_folders_by_path(
|
1061
1463
|
collection_data["item"], target_path)
|
1062
1464
|
if not folders:
|
1063
1465
|
raise ToolException(
|
@@ -1067,7 +1469,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1067
1469
|
collection_data["item"].append(request_data)
|
1068
1470
|
|
1069
1471
|
# Update collection
|
1070
|
-
response = self._make_request('PUT', f'/collections/{collection_id}',
|
1472
|
+
response = self._make_request('PUT', f'/collections/{self.collection_id}',
|
1071
1473
|
json={"collection": collection_data})
|
1072
1474
|
return json.dumps({"message": f"Request moved from '{source_path}' to '{target_path or 'root'}'"}, indent=2)
|
1073
1475
|
except Exception as e:
|
@@ -1075,958 +1477,75 @@ class PostmanApiWrapper(BaseToolApiWrapper):
|
|
1075
1477
|
logger.error(f"Exception when moving request: {stacktrace}")
|
1076
1478
|
raise ToolException(
|
1077
1479
|
f"Unable to move request '{source_path}': {str(e)}")
|
1078
|
-
|
1480
|
+
|
1079
1481
|
# =================================================================
|
1080
1482
|
# HELPER METHODS
|
1081
1483
|
# =================================================================
|
1082
1484
|
|
1083
|
-
def
|
1084
|
-
"""
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
requests.append(request_data)
|
1134
|
-
elif item.get('item'):
|
1135
|
-
# This is a folder, recurse
|
1136
|
-
requests.extend(self._extract_requests_from_items(
|
1137
|
-
item['item'], include_details))
|
1138
|
-
|
1139
|
-
return requests
|
1140
|
-
|
1141
|
-
def _search_requests_in_items(self, items: List[Dict], query: str, search_in: str, method: str = None) -> List[Dict]:
|
1142
|
-
"""Search for requests in items recursively."""
|
1143
|
-
results = []
|
1144
|
-
query_lower = query.lower()
|
1145
|
-
|
1146
|
-
for item in items:
|
1147
|
-
if item.get('request'):
|
1148
|
-
# This is a request
|
1149
|
-
request = item['request']
|
1150
|
-
matches = False
|
1151
|
-
|
1152
|
-
# Check method filter first
|
1153
|
-
if method and request.get('method', '').upper() != method.upper():
|
1154
|
-
continue
|
1155
|
-
|
1156
|
-
# Check search criteria
|
1157
|
-
if search_in == 'all' or search_in == 'name':
|
1158
|
-
if query_lower in item.get('name', '').lower():
|
1159
|
-
matches = True
|
1160
|
-
|
1161
|
-
if search_in == 'all' or search_in == 'url':
|
1162
|
-
url = request.get('url', '')
|
1163
|
-
if isinstance(url, dict):
|
1164
|
-
url = url.get('raw', '')
|
1165
|
-
if query_lower in url.lower():
|
1166
|
-
matches = True
|
1167
|
-
|
1168
|
-
if search_in == 'all' or search_in == 'description':
|
1169
|
-
description = item.get(
|
1170
|
-
'description', '') or request.get('description', '')
|
1171
|
-
if query_lower in description.lower():
|
1172
|
-
matches = True
|
1173
|
-
|
1174
|
-
if matches:
|
1175
|
-
results.append({
|
1176
|
-
"name": item.get('name'),
|
1177
|
-
"method": request.get('method'),
|
1178
|
-
"url": request.get('url'),
|
1179
|
-
"description": item.get('description') or request.get('description'),
|
1180
|
-
"path": self._get_item_path(items, item)
|
1181
|
-
})
|
1182
|
-
|
1183
|
-
elif item.get('item'):
|
1184
|
-
# This is a folder, recurse
|
1185
|
-
results.extend(self._search_requests_in_items(
|
1186
|
-
item['item'], query, search_in, method))
|
1187
|
-
|
1188
|
-
return results
|
1189
|
-
|
1190
|
-
def _get_item_path(self, root_items: List[Dict], target_item: Dict, current_path: str = "") -> str:
|
1191
|
-
"""Get the path of an item within the collection structure."""
|
1192
|
-
for item in root_items:
|
1193
|
-
item_path = f"{current_path}/{item['name']}" if current_path else item['name']
|
1194
|
-
|
1195
|
-
if item == target_item:
|
1196
|
-
return item_path
|
1197
|
-
|
1198
|
-
if item.get('item'):
|
1199
|
-
result = self._get_item_path(
|
1200
|
-
item['item'], target_item, item_path)
|
1201
|
-
if result:
|
1202
|
-
return result
|
1203
|
-
|
1204
|
-
return ""
|
1205
|
-
|
1206
|
-
def _find_request_by_path(self, items: List[Dict], request_path: str) -> Optional[Dict]:
|
1207
|
-
"""Find a request by its path."""
|
1208
|
-
path_parts = [part.strip()
|
1209
|
-
for part in request_path.split('/') if part.strip()]
|
1210
|
-
if not path_parts:
|
1211
|
-
return None
|
1212
|
-
|
1213
|
-
current_items = items
|
1214
|
-
|
1215
|
-
# Navigate through folders to the request
|
1216
|
-
for i, part in enumerate(path_parts):
|
1217
|
-
found = False
|
1218
|
-
for item in current_items:
|
1219
|
-
if item.get('name', '').lower() == part.lower():
|
1220
|
-
if i == len(path_parts) - 1:
|
1221
|
-
# This should be the request
|
1222
|
-
if item.get('request'):
|
1223
|
-
return item
|
1224
|
-
else:
|
1225
|
-
return None
|
1485
|
+
def get_request(self, request_path: str, **kwargs) -> str:
|
1486
|
+
"""Get a specific request by path.
|
1487
|
+
|
1488
|
+
Uses the _get_request_item_and_id helper to find the request and then fetches complete
|
1489
|
+
information using the Postman API endpoint for individual requests.
|
1490
|
+
"""
|
1491
|
+
try:
|
1492
|
+
# Get request item and ID
|
1493
|
+
_, request_id, _ = self._get_request_item_and_id(request_path)
|
1494
|
+
|
1495
|
+
# Fetch the complete request information from the API
|
1496
|
+
response = self._make_request(
|
1497
|
+
'GET', f'/collections/{self.collection_id}/requests/{request_id}'
|
1498
|
+
)
|
1499
|
+
|
1500
|
+
return json.dumps(response, indent=2)
|
1501
|
+
except Exception as e:
|
1502
|
+
stacktrace = format_exc()
|
1503
|
+
logger.error(f"Exception when getting request: {stacktrace}")
|
1504
|
+
raise ToolException(
|
1505
|
+
f"Unable to get request '{request_path}': {str(e)}")
|
1506
|
+
|
1507
|
+
def get_request_script(self, request_path: str, script_type: str = "prerequest", **kwargs) -> str:
|
1508
|
+
"""
|
1509
|
+
Get the script (pre-request or test) for a request by path.
|
1510
|
+
|
1511
|
+
Args:
|
1512
|
+
request_path: Path to the request within the collection
|
1513
|
+
script_type: The type of script to retrieve ("prerequest" or "test")
|
1514
|
+
|
1515
|
+
Returns:
|
1516
|
+
The script content as JSON string, or an error message if the script doesn't exist
|
1517
|
+
"""
|
1518
|
+
try:
|
1519
|
+
# Get the request ID and fetch current request data
|
1520
|
+
_, request_id, _ = self._get_request_item_and_id(request_path)
|
1521
|
+
|
1522
|
+
# Get current request to have the latest version with updated scripts
|
1523
|
+
current_request = self._make_request('GET', f'/collections/{self.collection_id}/requests/{request_id}')
|
1524
|
+
request_data = current_request.get("data", {})
|
1525
|
+
|
1526
|
+
# Find the script by type
|
1527
|
+
script_content = None
|
1528
|
+
for event in request_data.get("events", []):
|
1529
|
+
if event.get("listen") == script_type:
|
1530
|
+
script = event.get("script", {})
|
1531
|
+
exec_content = script.get("exec", [])
|
1532
|
+
if isinstance(exec_content, list):
|
1533
|
+
script_content = "\n".join(exec_content)
|
1226
1534
|
else:
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
if not path_parts:
|
1245
|
-
return False
|
1246
|
-
|
1247
|
-
if len(path_parts) == 1:
|
1248
|
-
# Remove from current level
|
1249
|
-
for i, item in enumerate(items):
|
1250
|
-
if item.get('name', '').lower() == path_parts[0].lower() and item.get('item') is not None:
|
1251
|
-
del items[i]
|
1252
|
-
return True
|
1253
|
-
return False
|
1254
|
-
else:
|
1255
|
-
# Navigate to parent folder
|
1256
|
-
parent_path = '/'.join(path_parts[:-1])
|
1257
|
-
parent_folders = self._find_folders_by_path(items, parent_path)
|
1258
|
-
if parent_folders:
|
1259
|
-
return self._remove_folder_by_path(parent_folders[0]['item'], path_parts[-1])
|
1260
|
-
return False
|
1261
|
-
|
1262
|
-
def _remove_request_by_path(self, items: List[Dict], request_path: str) -> bool:
|
1263
|
-
"""Remove a request by its path."""
|
1264
|
-
path_parts = [part.strip()
|
1265
|
-
for part in request_path.split('/') if part.strip()]
|
1266
|
-
if not path_parts:
|
1267
|
-
return False
|
1268
|
-
|
1269
|
-
if len(path_parts) == 1:
|
1270
|
-
# Remove from current level
|
1271
|
-
for i, item in enumerate(items):
|
1272
|
-
if item.get('name', '').lower() == path_parts[0].lower() and item.get('request'):
|
1273
|
-
del items[i]
|
1274
|
-
return True
|
1275
|
-
return False
|
1276
|
-
else:
|
1277
|
-
# Navigate to parent folder
|
1278
|
-
parent_path = '/'.join(path_parts[:-1])
|
1279
|
-
parent_folders = self._find_folders_by_path(items, parent_path)
|
1280
|
-
if parent_folders:
|
1281
|
-
return self._remove_request_by_path(parent_folders[0]['item'], path_parts[-1])
|
1282
|
-
return False
|
1283
|
-
|
1284
|
-
def _remove_item_ids(self, items: List[Dict]):
|
1285
|
-
"""Remove IDs from items recursively for duplication."""
|
1286
|
-
for item in items:
|
1287
|
-
if 'id' in item:
|
1288
|
-
del item['id']
|
1289
|
-
if item.get('item'):
|
1290
|
-
self._remove_item_ids(item['item'])
|
1291
|
-
|
1292
|
-
# =================================================================
|
1293
|
-
# ANALYSIS HELPER METHODS
|
1294
|
-
# =================================================================
|
1295
|
-
|
1296
|
-
def _perform_collection_analysis(self, collection: Dict) -> Dict:
|
1297
|
-
"""Perform comprehensive analysis of a collection."""
|
1298
|
-
collection_data = collection['collection']
|
1299
|
-
folders = self._analyze_folders(collection_data.get('item', []))
|
1300
|
-
total_requests = self._count_requests(collection_data.get('item', []))
|
1301
|
-
issues = self._identify_collection_issues(collection_data)
|
1302
|
-
score = self._calculate_quality_score(collection_data, folders, issues)
|
1303
|
-
recommendations = self._generate_recommendations(issues)
|
1304
|
-
|
1305
|
-
return {
|
1306
|
-
"collection_id": collection_data['info'].get('_postman_id', ''),
|
1307
|
-
"collection_name": collection_data['info'].get('name', ''),
|
1308
|
-
"total_requests": total_requests,
|
1309
|
-
"folders": folders,
|
1310
|
-
"issues": issues,
|
1311
|
-
"recommendations": recommendations,
|
1312
|
-
"score": score,
|
1313
|
-
"overall_security_score": self._calculate_overall_security_score(folders),
|
1314
|
-
"overall_performance_score": self._calculate_overall_performance_score(folders),
|
1315
|
-
"overall_documentation_score": self._calculate_overall_documentation_score(folders)
|
1316
|
-
}
|
1317
|
-
|
1318
|
-
def _analyze_folders(self, items: List[Dict], base_path: str = "") -> List[Dict]:
|
1319
|
-
"""Analyze all folders in a collection."""
|
1320
|
-
folders = []
|
1321
|
-
|
1322
|
-
for item in items:
|
1323
|
-
if item.get('item') is not None: # This is a folder
|
1324
|
-
folder_path = f"{base_path}/{item['name']}" if base_path else item['name']
|
1325
|
-
analysis = self._perform_folder_analysis(item, folder_path)
|
1326
|
-
folders.append(analysis)
|
1327
|
-
|
1328
|
-
# Recursively analyze subfolders
|
1329
|
-
subfolders = self._analyze_folders(item['item'], folder_path)
|
1330
|
-
folders.extend(subfolders)
|
1331
|
-
|
1332
|
-
return folders
|
1333
|
-
|
1334
|
-
def _perform_folder_analysis(self, folder: Dict, path: str) -> Dict:
|
1335
|
-
"""Perform analysis of a specific folder."""
|
1336
|
-
requests = self._analyze_requests(folder.get('item', []))
|
1337
|
-
request_count = self._count_requests(folder.get('item', []))
|
1338
|
-
issues = self._identify_folder_issues(folder, requests)
|
1339
|
-
|
1340
|
-
return {
|
1341
|
-
"name": folder['name'],
|
1342
|
-
"path": path,
|
1343
|
-
"request_count": request_count,
|
1344
|
-
"requests": requests,
|
1345
|
-
"issues": issues,
|
1346
|
-
"has_consistent_naming": self._check_consistent_naming(folder.get('item', [])),
|
1347
|
-
"has_proper_structure": bool(folder.get('description') and folder.get('item')),
|
1348
|
-
"auth_consistency": self._check_auth_consistency(requests),
|
1349
|
-
"avg_documentation_quality": self._calculate_avg_documentation_quality(requests),
|
1350
|
-
"avg_security_score": self._calculate_avg_security_score(requests),
|
1351
|
-
"avg_performance_score": self._calculate_avg_performance_score(requests)
|
1352
|
-
}
|
1353
|
-
|
1354
|
-
def _analyze_requests(self, items: List[Dict]) -> List[Dict]:
|
1355
|
-
"""Analyze requests within a folder."""
|
1356
|
-
requests = []
|
1357
|
-
|
1358
|
-
for item in items:
|
1359
|
-
if item.get('request'): # This is a request
|
1360
|
-
analysis = self._perform_request_analysis(item)
|
1361
|
-
requests.append(analysis)
|
1362
|
-
|
1363
|
-
return requests
|
1364
|
-
|
1365
|
-
def _perform_request_analysis(self, item: Dict) -> Dict:
|
1366
|
-
"""Perform comprehensive analysis of a specific request."""
|
1367
|
-
request = item['request']
|
1368
|
-
issues = []
|
1369
|
-
|
1370
|
-
# Basic checks
|
1371
|
-
has_auth = bool(request.get('auth')
|
1372
|
-
or self._has_auth_in_headers(request))
|
1373
|
-
has_description = bool(item.get('description')
|
1374
|
-
or request.get('description'))
|
1375
|
-
has_tests = bool([e for e in item.get('event', [])
|
1376
|
-
if e.get('listen') == 'test'])
|
1377
|
-
has_examples = bool(item.get('response', []))
|
1378
|
-
|
1379
|
-
# Enhanced analysis
|
1380
|
-
url = request.get('url', '')
|
1381
|
-
if isinstance(url, dict):
|
1382
|
-
url = url.get('raw', '')
|
1383
|
-
|
1384
|
-
has_hardcoded_url = self._detect_hardcoded_url(url)
|
1385
|
-
has_hardcoded_data = self._detect_hardcoded_data(request)
|
1386
|
-
has_proper_headers = self._validate_headers(request)
|
1387
|
-
has_variables = self._detect_variable_usage(request)
|
1388
|
-
has_error_handling = self._detect_error_handling(item)
|
1389
|
-
follows_naming_convention = self._validate_naming_convention(
|
1390
|
-
item['name'])
|
1391
|
-
has_security_issues = self._detect_security_issues(request)
|
1392
|
-
has_performance_issues = self._detect_performance_issues(request)
|
1393
|
-
|
1394
|
-
# Calculate scores
|
1395
|
-
security_score = self._calculate_security_score(
|
1396
|
-
request, has_auth, has_security_issues)
|
1397
|
-
performance_score = self._calculate_performance_score(
|
1398
|
-
request, has_performance_issues)
|
1399
|
-
|
1400
|
-
# Generate issues
|
1401
|
-
self._generate_request_issues(issues, item, {
|
1402
|
-
'has_description': has_description,
|
1403
|
-
'has_auth': has_auth,
|
1404
|
-
'has_tests': has_tests,
|
1405
|
-
'has_hardcoded_url': has_hardcoded_url,
|
1406
|
-
'has_hardcoded_data': has_hardcoded_data,
|
1407
|
-
'has_proper_headers': has_proper_headers,
|
1408
|
-
'has_security_issues': has_security_issues,
|
1409
|
-
'follows_naming_convention': follows_naming_convention
|
1410
|
-
})
|
1411
|
-
|
1412
|
-
return {
|
1413
|
-
"name": item['name'],
|
1414
|
-
"method": request.get('method'),
|
1415
|
-
"url": url,
|
1416
|
-
"has_auth": has_auth,
|
1417
|
-
"has_description": has_description,
|
1418
|
-
"has_tests": has_tests,
|
1419
|
-
"has_examples": has_examples,
|
1420
|
-
"issues": issues,
|
1421
|
-
"has_hardcoded_url": has_hardcoded_url,
|
1422
|
-
"has_hardcoded_data": has_hardcoded_data,
|
1423
|
-
"has_proper_headers": has_proper_headers,
|
1424
|
-
"has_variables": has_variables,
|
1425
|
-
"has_error_handling": has_error_handling,
|
1426
|
-
"follows_naming_convention": follows_naming_convention,
|
1427
|
-
"has_security_issues": has_security_issues,
|
1428
|
-
"has_performance_issues": has_performance_issues,
|
1429
|
-
"auth_type": request.get('auth', {}).get('type'),
|
1430
|
-
"response_examples": len(item.get('response', [])),
|
1431
|
-
"test_coverage": self._assess_test_coverage(item),
|
1432
|
-
"documentation_quality": self._assess_documentation_quality(item),
|
1433
|
-
"security_score": security_score,
|
1434
|
-
"performance_score": performance_score
|
1435
|
-
}
|
1436
|
-
|
1437
|
-
def _count_requests(self, items: List[Dict]) -> int:
|
1438
|
-
"""Count total requests in items."""
|
1439
|
-
count = 0
|
1440
|
-
for item in items:
|
1441
|
-
if item.get('request'):
|
1442
|
-
count += 1
|
1443
|
-
elif item.get('item'):
|
1444
|
-
count += self._count_requests(item['item'])
|
1445
|
-
return count
|
1446
|
-
|
1447
|
-
def _has_auth_in_headers(self, request: Dict) -> bool:
|
1448
|
-
"""Check if request has authentication in headers."""
|
1449
|
-
headers = request.get('header', [])
|
1450
|
-
auth_headers = ['authorization', 'x-api-key', 'x-auth-token']
|
1451
|
-
return any(h.get('key', '').lower() in auth_headers for h in headers)
|
1452
|
-
|
1453
|
-
def _detect_hardcoded_url(self, url: str) -> bool:
|
1454
|
-
"""Detect hardcoded URLs that should use variables."""
|
1455
|
-
hardcoded_patterns = [
|
1456
|
-
r'^https?://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', # IP addresses
|
1457
|
-
r'^https?://localhost', # localhost
|
1458
|
-
# Direct domains
|
1459
|
-
r'^https?://[a-zA-Z0-9.-]+\.(com|org|net|io|dev)',
|
1460
|
-
r'api\.example\.com', # Example domains
|
1461
|
-
r'staging\.|dev\.|test\.' # Environment-specific
|
1462
|
-
]
|
1463
|
-
return any(re.search(pattern, url) for pattern in hardcoded_patterns) and '{{' not in url
|
1464
|
-
|
1465
|
-
def _detect_hardcoded_data(self, request: Dict) -> bool:
|
1466
|
-
"""Detect hardcoded data in request body and headers."""
|
1467
|
-
# Check headers
|
1468
|
-
headers = request.get('header', [])
|
1469
|
-
has_hardcoded_headers = any(
|
1470
|
-
('token' in h.get('key', '').lower() or
|
1471
|
-
'key' in h.get('key', '').lower() or
|
1472
|
-
'secret' in h.get('key', '').lower()) and
|
1473
|
-
'{{' not in h.get('value', '')
|
1474
|
-
for h in headers
|
1475
|
-
)
|
1476
|
-
|
1477
|
-
# Check body
|
1478
|
-
has_hardcoded_body = False
|
1479
|
-
body = request.get('body', {})
|
1480
|
-
if body.get('raw'):
|
1481
|
-
try:
|
1482
|
-
body_data = json.loads(body['raw'])
|
1483
|
-
has_hardcoded_body = self._contains_hardcoded_values(body_data)
|
1484
|
-
except json.JSONDecodeError:
|
1485
|
-
# If not JSON, check for common patterns
|
1486
|
-
has_hardcoded_body = re.search(
|
1487
|
-
r'("api_key"|"token"|"password"):\s*"[^{]', body['raw']) is not None
|
1488
|
-
|
1489
|
-
return has_hardcoded_headers or has_hardcoded_body
|
1490
|
-
|
1491
|
-
def _contains_hardcoded_values(self, obj: Any) -> bool:
|
1492
|
-
"""Check if object contains hardcoded values that should be variables."""
|
1493
|
-
if not isinstance(obj, dict):
|
1494
|
-
return False
|
1495
|
-
|
1496
|
-
for key, value in obj.items():
|
1497
|
-
if isinstance(value, str):
|
1498
|
-
# Check for sensitive keys
|
1499
|
-
if key.lower() in ['token', 'key', 'secret', 'password', 'api_key', 'client_id', 'client_secret']:
|
1500
|
-
if '{{' not in value:
|
1501
|
-
return True
|
1502
|
-
# Check for email patterns, URLs
|
1503
|
-
if re.search(r'@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', value) or value.startswith('http'):
|
1504
|
-
if '{{' not in value:
|
1505
|
-
return True
|
1506
|
-
elif isinstance(value, dict):
|
1507
|
-
if self._contains_hardcoded_values(value):
|
1508
|
-
return True
|
1509
|
-
|
1510
|
-
return False
|
1511
|
-
|
1512
|
-
def _validate_headers(self, request: Dict) -> bool:
|
1513
|
-
"""Validate request headers."""
|
1514
|
-
headers = request.get('header', [])
|
1515
|
-
header_names = [h.get('key', '').lower() for h in headers]
|
1516
|
-
method = request.get('method', '').upper()
|
1517
|
-
|
1518
|
-
# Check for essential headers
|
1519
|
-
if method in ['POST', 'PUT', 'PATCH'] and request.get('body'):
|
1520
|
-
if 'content-type' not in header_names:
|
1521
|
-
return False
|
1522
|
-
|
1523
|
-
if method in ['GET', 'POST', 'PUT', 'PATCH']:
|
1524
|
-
if 'accept' not in header_names:
|
1525
|
-
return False
|
1526
|
-
|
1527
|
-
return True
|
1528
|
-
|
1529
|
-
def _detect_variable_usage(self, request: Dict) -> bool:
|
1530
|
-
"""Detect variable usage in request."""
|
1531
|
-
url = request.get('url', '')
|
1532
|
-
if isinstance(url, dict):
|
1533
|
-
url = url.get('raw', '')
|
1534
|
-
|
1535
|
-
has_url_variables = '{{' in url
|
1536
|
-
has_header_variables = any('{{' in h.get('value', '')
|
1537
|
-
for h in request.get('header', []))
|
1538
|
-
|
1539
|
-
has_body_variables = False
|
1540
|
-
body = request.get('body', {})
|
1541
|
-
if body.get('raw'):
|
1542
|
-
has_body_variables = '{{' in body['raw']
|
1543
|
-
|
1544
|
-
return has_url_variables or has_header_variables or has_body_variables
|
1545
|
-
|
1546
|
-
def _detect_error_handling(self, item: Dict) -> bool:
|
1547
|
-
"""Detect error handling in tests."""
|
1548
|
-
test_scripts = [e for e in item.get(
|
1549
|
-
'event', []) if e.get('listen') == 'test']
|
1550
|
-
|
1551
|
-
for script in test_scripts:
|
1552
|
-
script_code = '\n'.join(script.get('script', {}).get('exec', []))
|
1553
|
-
if ('4' in script_code or '5' in script_code or
|
1554
|
-
'error' in script_code.lower() or 'fail' in script_code.lower()):
|
1555
|
-
return True
|
1556
|
-
|
1557
|
-
return False
|
1558
|
-
|
1559
|
-
def _validate_naming_convention(self, name: str) -> bool:
|
1560
|
-
"""Validate naming convention."""
|
1561
|
-
has_consistent_case = re.match(
|
1562
|
-
r'^[a-zA-Z][a-zA-Z0-9\s\-_]*$', name) is not None
|
1563
|
-
has_descriptive_name = len(
|
1564
|
-
name) > 3 and 'test' not in name.lower() and 'temp' not in name.lower()
|
1565
|
-
return has_consistent_case and has_descriptive_name
|
1566
|
-
|
1567
|
-
def _detect_security_issues(self, request: Dict) -> bool:
|
1568
|
-
"""Detect security issues."""
|
1569
|
-
url = request.get('url', '')
|
1570
|
-
if isinstance(url, dict):
|
1571
|
-
url = url.get('raw', '')
|
1572
|
-
|
1573
|
-
# Check for exposed credentials in URL
|
1574
|
-
if re.search(r'[?&](token|key|password|secret)=([^&\s]+)', url):
|
1575
|
-
return True
|
1576
|
-
|
1577
|
-
# Check for weak authentication
|
1578
|
-
auth = request.get('auth', {})
|
1579
|
-
if auth.get('type') == 'basic' and not url.startswith('https'):
|
1580
|
-
return True
|
1581
|
-
|
1582
|
-
# Check headers for exposed credentials
|
1583
|
-
headers = request.get('header', [])
|
1584
|
-
return any('secret' in h.get('key', '').lower() or 'password' in h.get('key', '').lower()
|
1585
|
-
for h in headers)
|
1586
|
-
|
1587
|
-
def _detect_performance_issues(self, request: Dict) -> bool:
|
1588
|
-
"""Detect performance issues."""
|
1589
|
-
# Large request body
|
1590
|
-
body = request.get('body', {})
|
1591
|
-
if body.get('raw') and len(body['raw']) > 10000:
|
1592
|
-
return True
|
1593
|
-
|
1594
|
-
# Too many headers
|
1595
|
-
if len(request.get('header', [])) > 20:
|
1596
|
-
return True
|
1597
|
-
|
1598
|
-
# Too many query parameters
|
1599
|
-
url = request.get('url', '')
|
1600
|
-
if isinstance(url, dict):
|
1601
|
-
url = url.get('raw', '')
|
1602
|
-
|
1603
|
-
query_params = url.split('?')[1] if '?' in url else ''
|
1604
|
-
if query_params and len(query_params.split('&')) > 15:
|
1605
|
-
return True
|
1606
|
-
|
1607
|
-
return False
|
1608
|
-
|
1609
|
-
def _calculate_security_score(self, request: Dict, has_auth: bool, has_security_issues: bool) -> int:
|
1610
|
-
"""Calculate security score."""
|
1611
|
-
score = 100
|
1612
|
-
method = request.get('method', '').upper()
|
1613
|
-
|
1614
|
-
if not has_auth and method in ['POST', 'PUT', 'PATCH', 'DELETE']:
|
1615
|
-
score -= 40
|
1616
|
-
|
1617
|
-
if has_security_issues:
|
1618
|
-
score -= 30
|
1619
|
-
|
1620
|
-
url = request.get('url', '')
|
1621
|
-
if isinstance(url, dict):
|
1622
|
-
url = url.get('raw', '')
|
1623
|
-
|
1624
|
-
if url.startswith('http://'):
|
1625
|
-
score -= 20
|
1626
|
-
|
1627
|
-
auth = request.get('auth', {})
|
1628
|
-
if auth.get('type') == 'basic':
|
1629
|
-
score -= 10
|
1630
|
-
|
1631
|
-
return max(0, score)
|
1632
|
-
|
1633
|
-
def _calculate_performance_score(self, request: Dict, has_performance_issues: bool) -> int:
|
1634
|
-
"""Calculate performance score."""
|
1635
|
-
score = 100
|
1636
|
-
|
1637
|
-
if has_performance_issues:
|
1638
|
-
score -= 50
|
1639
|
-
|
1640
|
-
headers = request.get('header', [])
|
1641
|
-
header_names = [h.get('key', '').lower() for h in headers]
|
1642
|
-
|
1643
|
-
if 'cache-control' not in header_names:
|
1644
|
-
score -= 10
|
1645
|
-
|
1646
|
-
if 'accept-encoding' not in header_names:
|
1647
|
-
score -= 10
|
1648
|
-
|
1649
|
-
return max(0, score)
|
1650
|
-
|
1651
|
-
def _assess_test_coverage(self, item: Dict) -> str:
|
1652
|
-
"""Assess test coverage."""
|
1653
|
-
test_scripts = [e for e in item.get(
|
1654
|
-
'event', []) if e.get('listen') == 'test']
|
1655
|
-
|
1656
|
-
if not test_scripts:
|
1657
|
-
return 'none'
|
1658
|
-
|
1659
|
-
all_test_code = '\n'.join([
|
1660
|
-
'\n'.join(script.get('script', {}).get('exec', []))
|
1661
|
-
for script in test_scripts
|
1662
|
-
])
|
1663
|
-
|
1664
|
-
checks = [
|
1665
|
-
'pm.response.code' in all_test_code or 'status' in all_test_code,
|
1666
|
-
'responseTime' in all_test_code,
|
1667
|
-
'pm.response.json' in all_test_code or 'body' in all_test_code,
|
1668
|
-
'4' in all_test_code or '5' in all_test_code
|
1669
|
-
]
|
1670
|
-
|
1671
|
-
check_count = sum(checks)
|
1672
|
-
|
1673
|
-
if check_count >= 3:
|
1674
|
-
return 'comprehensive'
|
1675
|
-
elif check_count >= 1:
|
1676
|
-
return 'basic'
|
1677
|
-
|
1678
|
-
return 'none'
|
1679
|
-
|
1680
|
-
def _assess_documentation_quality(self, item: Dict) -> str:
|
1681
|
-
"""Assess documentation quality."""
|
1682
|
-
description = item.get('description', '') or item.get(
|
1683
|
-
'request', {}).get('description', '')
|
1684
|
-
|
1685
|
-
if not description:
|
1686
|
-
return 'none'
|
1687
|
-
|
1688
|
-
description_lower = description.lower()
|
1689
|
-
quality_factors = [
|
1690
|
-
'parameter' in description_lower,
|
1691
|
-
'response' in description_lower,
|
1692
|
-
'example' in description_lower,
|
1693
|
-
'auth' in description_lower,
|
1694
|
-
'error' in description_lower
|
1695
|
-
]
|
1696
|
-
|
1697
|
-
factor_count = sum(quality_factors)
|
1698
|
-
|
1699
|
-
if factor_count >= 4:
|
1700
|
-
return 'excellent'
|
1701
|
-
elif factor_count >= 2:
|
1702
|
-
return 'good'
|
1703
|
-
elif factor_count >= 1 or len(description) > 50:
|
1704
|
-
return 'minimal'
|
1705
|
-
|
1706
|
-
return 'none'
|
1707
|
-
|
1708
|
-
def _check_consistent_naming(self, items: List[Dict]) -> bool:
|
1709
|
-
"""Check if items have consistent naming."""
|
1710
|
-
if len(items) <= 1:
|
1711
|
-
return True
|
1712
|
-
|
1713
|
-
naming_patterns = []
|
1714
|
-
for item in items:
|
1715
|
-
name = item.get('name', '').lower()
|
1716
|
-
if re.match(r'^[a-z][a-z0-9]*(_[a-z0-9]+)*$', name):
|
1717
|
-
naming_patterns.append('snake_case')
|
1718
|
-
elif re.match(r'^[a-z][a-zA-Z0-9]*$', name):
|
1719
|
-
naming_patterns.append('camelCase')
|
1720
|
-
elif re.match(r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$', name):
|
1721
|
-
naming_patterns.append('kebab-case')
|
1722
|
-
else:
|
1723
|
-
naming_patterns.append('mixed')
|
1724
|
-
|
1725
|
-
unique_patterns = set(naming_patterns)
|
1726
|
-
return len(unique_patterns) == 1 and 'mixed' not in unique_patterns
|
1727
|
-
|
1728
|
-
def _check_auth_consistency(self, requests: List[Dict]) -> str:
|
1729
|
-
"""Check authentication consistency across requests."""
|
1730
|
-
if not requests:
|
1731
|
-
return 'none'
|
1732
|
-
|
1733
|
-
auth_types = set(req.get('auth_type') or 'none' for req in requests)
|
1734
|
-
|
1735
|
-
if len(auth_types) == 1:
|
1736
|
-
return 'none' if 'none' in auth_types else 'consistent'
|
1737
|
-
|
1738
|
-
return 'mixed'
|
1739
|
-
|
1740
|
-
def _calculate_avg_documentation_quality(self, requests: List[Dict]) -> int:
|
1741
|
-
"""Calculate average documentation quality score."""
|
1742
|
-
if not requests:
|
1743
|
-
return 0
|
1744
|
-
|
1745
|
-
quality_scores = {
|
1746
|
-
'excellent': 100,
|
1747
|
-
'good': 75,
|
1748
|
-
'minimal': 50,
|
1749
|
-
'none': 0
|
1750
|
-
}
|
1751
|
-
|
1752
|
-
scores = [quality_scores.get(
|
1753
|
-
req.get('documentation_quality', 'none'), 0) for req in requests]
|
1754
|
-
return round(sum(scores) / len(scores))
|
1755
|
-
|
1756
|
-
def _calculate_avg_security_score(self, requests: List[Dict]) -> int:
|
1757
|
-
"""Calculate average security score."""
|
1758
|
-
if not requests:
|
1759
|
-
return 0
|
1760
|
-
|
1761
|
-
scores = [req.get('security_score', 0) for req in requests]
|
1762
|
-
return round(sum(scores) / len(scores))
|
1763
|
-
|
1764
|
-
def _calculate_avg_performance_score(self, requests: List[Dict]) -> int:
|
1765
|
-
"""Calculate average performance score."""
|
1766
|
-
if not requests:
|
1767
|
-
return 0
|
1768
|
-
|
1769
|
-
scores = [req.get('performance_score', 0) for req in requests]
|
1770
|
-
return round(sum(scores) / len(scores))
|
1771
|
-
|
1772
|
-
def _identify_collection_issues(self, collection_data: Dict) -> List[Dict]:
|
1773
|
-
"""Identify collection-level issues."""
|
1774
|
-
issues = []
|
1775
|
-
|
1776
|
-
if not collection_data.get('info', {}).get('description'):
|
1777
|
-
issues.append({
|
1778
|
-
'type': 'warning',
|
1779
|
-
'severity': 'medium',
|
1780
|
-
'message': 'Collection lacks description',
|
1781
|
-
'location': 'Collection root',
|
1782
|
-
'suggestion': 'Add a description explaining the purpose of this collection'
|
1783
|
-
})
|
1784
|
-
|
1785
|
-
if not collection_data.get('auth'):
|
1786
|
-
issues.append({
|
1787
|
-
'type': 'info',
|
1788
|
-
'severity': 'low',
|
1789
|
-
'message': 'Collection lacks default authentication',
|
1790
|
-
'location': 'Collection root',
|
1791
|
-
'suggestion': 'Consider setting up collection-level authentication'
|
1792
|
-
})
|
1793
|
-
|
1794
|
-
return issues
|
1795
|
-
|
1796
|
-
def _identify_folder_issues(self, folder: Dict, requests: List[Dict]) -> List[Dict]:
|
1797
|
-
"""Identify folder-level issues."""
|
1798
|
-
issues = []
|
1799
|
-
|
1800
|
-
if not folder.get('description'):
|
1801
|
-
issues.append({
|
1802
|
-
'type': 'warning',
|
1803
|
-
'severity': 'low',
|
1804
|
-
'message': 'Folder lacks description',
|
1805
|
-
'location': folder['name'],
|
1806
|
-
'suggestion': 'Add a description explaining the purpose of this folder'
|
1807
|
-
})
|
1808
|
-
|
1809
|
-
if not requests and (not folder.get('item') or len(folder['item']) == 0):
|
1810
|
-
issues.append({
|
1811
|
-
'type': 'warning',
|
1812
|
-
'severity': 'medium',
|
1813
|
-
'message': 'Empty folder',
|
1814
|
-
'location': folder['name'],
|
1815
|
-
'suggestion': 'Consider removing empty folders or adding requests'
|
1816
|
-
})
|
1817
|
-
|
1818
|
-
return issues
|
1819
|
-
|
1820
|
-
def _generate_request_issues(self, issues: List[Dict], item: Dict, analysis: Dict):
|
1821
|
-
"""Generate request-specific issues."""
|
1822
|
-
if not analysis['has_description']:
|
1823
|
-
issues.append({
|
1824
|
-
'type': 'warning',
|
1825
|
-
'severity': 'medium',
|
1826
|
-
'message': 'Request lacks description',
|
1827
|
-
'location': item['name'],
|
1828
|
-
'suggestion': 'Add a clear description explaining what this request does'
|
1829
|
-
})
|
1830
|
-
|
1831
|
-
if not analysis['has_auth'] and item['request']['method'] in ['POST', 'PUT', 'PATCH', 'DELETE']:
|
1832
|
-
issues.append({
|
1833
|
-
'type': 'warning',
|
1834
|
-
'severity': 'high',
|
1835
|
-
'message': 'Sensitive operation without authentication',
|
1836
|
-
'location': item['name'],
|
1837
|
-
'suggestion': 'Add authentication for this request'
|
1838
|
-
})
|
1839
|
-
|
1840
|
-
if not analysis['has_tests']:
|
1841
|
-
issues.append({
|
1842
|
-
'type': 'info',
|
1843
|
-
'severity': 'high',
|
1844
|
-
'message': 'Request lacks test scripts',
|
1845
|
-
'location': item['name'],
|
1846
|
-
'suggestion': 'Add test scripts to validate response'
|
1847
|
-
})
|
1848
|
-
|
1849
|
-
if analysis['has_hardcoded_url']:
|
1850
|
-
issues.append({
|
1851
|
-
'type': 'warning',
|
1852
|
-
'severity': 'high',
|
1853
|
-
'message': 'Request contains hardcoded URL',
|
1854
|
-
'location': item['name'],
|
1855
|
-
'suggestion': 'Replace hardcoded URLs with environment variables'
|
1856
|
-
})
|
1857
|
-
|
1858
|
-
if analysis['has_security_issues']:
|
1859
|
-
issues.append({
|
1860
|
-
'type': 'error',
|
1861
|
-
'severity': 'high',
|
1862
|
-
'message': 'Security vulnerabilities detected',
|
1863
|
-
'location': item['name'],
|
1864
|
-
'suggestion': 'Address security issues such as exposed credentials'
|
1865
|
-
})
|
1866
|
-
|
1867
|
-
def _calculate_quality_score(self, collection_data: Dict, folders: List[Dict], issues: List[Dict]) -> int:
|
1868
|
-
"""Calculate quality score (0-100)."""
|
1869
|
-
score = 100
|
1870
|
-
|
1871
|
-
# Deduct points for issues
|
1872
|
-
for issue in issues:
|
1873
|
-
severity = issue.get('severity', 'low')
|
1874
|
-
if severity == 'high':
|
1875
|
-
score -= 10
|
1876
|
-
elif severity == 'medium':
|
1877
|
-
score -= 5
|
1878
|
-
elif severity == 'low':
|
1879
|
-
score -= 2
|
1880
|
-
|
1881
|
-
# Deduct points for folder and request issues
|
1882
|
-
for folder in folders:
|
1883
|
-
for issue in folder.get('issues', []):
|
1884
|
-
severity = issue.get('severity', 'low')
|
1885
|
-
if severity == 'high':
|
1886
|
-
score -= 5
|
1887
|
-
elif severity == 'medium':
|
1888
|
-
score -= 3
|
1889
|
-
elif severity == 'low':
|
1890
|
-
score -= 1
|
1891
|
-
|
1892
|
-
for request in folder.get('requests', []):
|
1893
|
-
for issue in request.get('issues', []):
|
1894
|
-
severity = issue.get('severity', 'low')
|
1895
|
-
if severity == 'high':
|
1896
|
-
score -= 3
|
1897
|
-
elif severity == 'medium':
|
1898
|
-
score -= 2
|
1899
|
-
elif severity == 'low':
|
1900
|
-
score -= 1
|
1901
|
-
|
1902
|
-
return max(0, min(100, score))
|
1903
|
-
|
1904
|
-
def _generate_recommendations(self, issues: List[Dict]) -> List[str]:
|
1905
|
-
"""Generate recommendations based on issues."""
|
1906
|
-
recommendations = []
|
1907
|
-
suggestion_counts = {}
|
1908
|
-
|
1909
|
-
# Count similar suggestions
|
1910
|
-
for issue in issues:
|
1911
|
-
suggestion = issue.get('suggestion', '')
|
1912
|
-
if suggestion:
|
1913
|
-
suggestion_counts[suggestion] = suggestion_counts.get(
|
1914
|
-
suggestion, 0) + 1
|
1915
|
-
|
1916
|
-
# Generate recommendations from most common suggestions
|
1917
|
-
sorted_suggestions = sorted(
|
1918
|
-
suggestion_counts.items(), key=lambda x: x[1], reverse=True)[:10]
|
1919
|
-
|
1920
|
-
for suggestion, count in sorted_suggestions:
|
1921
|
-
if count > 1:
|
1922
|
-
recommendations.append(f"{suggestion} ({count} instances)")
|
1923
|
-
else:
|
1924
|
-
recommendations.append(suggestion)
|
1925
|
-
|
1926
|
-
return recommendations
|
1927
|
-
|
1928
|
-
def _calculate_overall_security_score(self, folders: List[Dict]) -> int:
|
1929
|
-
"""Calculate overall security score."""
|
1930
|
-
if not folders:
|
1931
|
-
return 0
|
1932
|
-
|
1933
|
-
scores = []
|
1934
|
-
for folder in folders:
|
1935
|
-
avg_score = folder.get('avg_security_score', 0)
|
1936
|
-
if avg_score > 0:
|
1937
|
-
scores.append(avg_score)
|
1938
|
-
|
1939
|
-
return round(sum(scores) / len(scores)) if scores else 0
|
1940
|
-
|
1941
|
-
def _calculate_overall_performance_score(self, folders: List[Dict]) -> int:
|
1942
|
-
"""Calculate overall performance score."""
|
1943
|
-
if not folders:
|
1944
|
-
return 0
|
1945
|
-
|
1946
|
-
scores = []
|
1947
|
-
for folder in folders:
|
1948
|
-
avg_score = folder.get('avg_performance_score', 0)
|
1949
|
-
if avg_score > 0:
|
1950
|
-
scores.append(avg_score)
|
1951
|
-
|
1952
|
-
return round(sum(scores) / len(scores)) if scores else 0
|
1953
|
-
|
1954
|
-
def _calculate_overall_documentation_score(self, folders: List[Dict]) -> int:
|
1955
|
-
"""Calculate overall documentation score."""
|
1956
|
-
if not folders:
|
1957
|
-
return 0
|
1958
|
-
|
1959
|
-
scores = []
|
1960
|
-
for folder in folders:
|
1961
|
-
avg_score = folder.get('avg_documentation_quality', 0)
|
1962
|
-
if avg_score > 0:
|
1963
|
-
scores.append(avg_score)
|
1964
|
-
|
1965
|
-
return round(sum(scores) / len(scores)) if scores else 0
|
1966
|
-
|
1967
|
-
def _generate_improvements(self, analysis: Dict) -> List[Dict]:
|
1968
|
-
"""Generate improvement suggestions with enhanced analysis."""
|
1969
|
-
improvements = []
|
1970
|
-
|
1971
|
-
# Collection-level improvements
|
1972
|
-
if analysis['score'] < 80:
|
1973
|
-
improvements.append({
|
1974
|
-
'id': 'collection-quality',
|
1975
|
-
'title': 'Improve Overall Collection Quality',
|
1976
|
-
'description': f"Collection quality score is {analysis['score']}/100. Focus on addressing high-priority issues.",
|
1977
|
-
'priority': 'high',
|
1978
|
-
'category': 'quality',
|
1979
|
-
'impact': 'high'
|
1980
|
-
})
|
1981
|
-
|
1982
|
-
if analysis['overall_security_score'] < 70:
|
1983
|
-
improvements.append({
|
1984
|
-
'id': 'security-enhancement',
|
1985
|
-
'title': 'Enhance Security Practices',
|
1986
|
-
'description': f"Security score is {analysis['overall_security_score']}/100. Review authentication and data handling.",
|
1987
|
-
'priority': 'high',
|
1988
|
-
'category': 'security',
|
1989
|
-
'impact': 'high'
|
1990
|
-
})
|
1991
|
-
|
1992
|
-
if analysis['overall_documentation_score'] < 60:
|
1993
|
-
improvements.append({
|
1994
|
-
'id': 'documentation-improvement',
|
1995
|
-
'title': 'Improve Documentation',
|
1996
|
-
'description': f"Documentation score is {analysis['overall_documentation_score']}/100. Add descriptions and examples.",
|
1997
|
-
'priority': 'medium',
|
1998
|
-
'category': 'documentation',
|
1999
|
-
'impact': 'medium'
|
2000
|
-
})
|
2001
|
-
|
2002
|
-
# Add specific improvements based on common issues
|
2003
|
-
issue_counts = {}
|
2004
|
-
for folder in analysis.get('folders', []):
|
2005
|
-
for request in folder.get('requests', []):
|
2006
|
-
for issue in request.get('issues', []):
|
2007
|
-
issue_type = issue.get('message', '')
|
2008
|
-
issue_counts[issue_type] = issue_counts.get(
|
2009
|
-
issue_type, 0) + 1
|
2010
|
-
|
2011
|
-
# Generate improvements for most common issues
|
2012
|
-
if issue_counts.get('Request lacks test scripts', 0) > 3:
|
2013
|
-
improvements.append({
|
2014
|
-
'id': 'add-test-scripts',
|
2015
|
-
'title': 'Add Test Scripts to Requests',
|
2016
|
-
'description': f"Found {issue_counts['Request lacks test scripts']} requests without test scripts.",
|
2017
|
-
'priority': 'medium',
|
2018
|
-
'category': 'testing',
|
2019
|
-
'impact': 'medium'
|
2020
|
-
})
|
2021
|
-
|
2022
|
-
if issue_counts.get('Request contains hardcoded URL', 0) > 2:
|
2023
|
-
improvements.append({
|
2024
|
-
'id': 'use-environment-variables',
|
2025
|
-
'title': 'Use Environment Variables',
|
2026
|
-
'description': f"Found {issue_counts['Request contains hardcoded URL']} requests with hardcoded URLs.",
|
2027
|
-
'priority': 'high',
|
2028
|
-
'category': 'maintainability',
|
2029
|
-
'impact': 'high'
|
2030
|
-
})
|
2031
|
-
|
2032
|
-
return improvements
|
1535
|
+
script_content = str(exec_content)
|
1536
|
+
break
|
1537
|
+
|
1538
|
+
if script_content is None:
|
1539
|
+
return json.dumps({"success": False, "message": f"No {script_type} script found for request '{request_path}'"}, indent=2)
|
1540
|
+
|
1541
|
+
return json.dumps({
|
1542
|
+
"success": True,
|
1543
|
+
"script_type": script_type,
|
1544
|
+
"script_content": script_content,
|
1545
|
+
"request_path": request_path
|
1546
|
+
}, indent=2)
|
1547
|
+
|
1548
|
+
except Exception as e:
|
1549
|
+
stacktrace = format_exc()
|
1550
|
+
logger.error(f"Exception when getting request {script_type} script: {stacktrace}")
|
1551
|
+
raise ToolException(f"Unable to get {script_type} script for request '{request_path}': {str(e)}")
|