synapse-hub 2.1.1 → 2.2.1
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.
- package/.agents/agents/orchestrator.md +7 -0
- package/.agents/rules/lessons-learned.md +1 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +1 -1
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +1 -2
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +11 -4
- package/.agents/skills/architecture-decision-records/SKILL.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +32 -11
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/clean-pom-crafter/SKILL.md +1 -4
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +1 -1
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +1 -3
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +1 -1
- package/.agents/skills/github-actions-templates/SKILL.md +1 -1
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +1 -3
- package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
- package/.agents/skills/lam-script-generator/SKILL.md +1 -2
- package/.agents/skills/playwright-live-interaction/SKILL.md +1 -4
- package/.agents/skills/security-dast-sentinel/SKILL.md +1 -1
- package/.agents/skills/self-healing-locator/SKILL.md +1 -2
- package/.agents/skills/skillspector-security-guard/SKILL.md +115 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/visual-regression-sentinel/SKILL.md +1 -1
- package/.agents/skills/wcag-audit-patterns/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/AGENTS.md +36 -0
- package/LICENSE +22 -0
- package/README.md +171 -107
- package/bin/agent-qa.js +4 -0
- package/package.json +3 -2
- package/skills_manifest.json +42 -29
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +15 -1
- package/templates/ai_bridges/CLAUDE.md +31 -0
- package/templates/ai_bridges/copilot-instructions.md +25 -0
- package/templates/ai_bridges/cursor_synapse_qa.mdc +26 -0
- package/templates/ai_bridges/windsurfrules +25 -0
- package/templates/ci/github-actions-synapse.yml +28 -0
- package/templates/ci/gitlab-ci-synapse.yml +16 -0
- package/tools/TOOLS_RUNBOOK.md +223 -12
- package/tools/author_integrity.py +151 -0
- package/tools/browser_helpers.js +2 -2
- package/tools/bump_version.py +8 -0
- package/tools/cli.py +100 -4
- package/tools/export_postman_collection.py +535 -218
- package/tools/generate_jmx_plan.py +672 -102
- package/tools/git_pre_commit_guard.py +21 -0
- package/tools/jtl_analyzer.py +474 -0
- package/tools/mcp_excel_server.ps1 +1 -1
- package/tools/mcp_server.py +1 -1
- package/tools/newman_analyzer.py +385 -0
- package/tools/setup_project.py +203 -18
- package/tools/skillspector.py +1145 -0
- package/tools/telemetry_correlator.py +351 -0
- package/tools/templates/jmeter-distributed/README.md +54 -0
- package/tools/templates/jmeter-distributed/docker-compose.yml +83 -0
- package/tools/update_sentinel.py +313 -0
- package/tools/verify_all.py +30 -1
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
tools/export_postman_collection.py — Enterprise API to Postman Collection v2.1.0 Exporter
|
|
5
|
-
|
|
6
|
-
Converts OpenAPI / Swagger specifications, JSON endpoint schemas,
|
|
7
|
-
into
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
3. Pre-request Scripts: Dynamic request timestamps, UUIDs, and automated
|
|
4
|
+
tools/export_postman_collection.py — Enterprise API to Postman Collection v2.1.0 Exporter (Postman Pinnacle)
|
|
5
|
+
-----------------------------------------------------------------------------------------------------------
|
|
6
|
+
Converts OpenAPI / Swagger specifications, JSON endpoint schemas, HTTP Archives (HAR),
|
|
7
|
+
or Natural Language Domain archetypes into production-ready Postman Collections v2.1.0.
|
|
8
|
+
|
|
9
|
+
Pinnacle Enterprise Capabilities:
|
|
10
|
+
1. Multi-Format Ingestion: OpenAPI 3.0 / Swagger 2.0, HAR traffic traces, or Zero-Input Domain Synthesis.
|
|
11
|
+
2. Autonomous Zero-Input Synthesis: Generates complete multi-tier API collections from scratch
|
|
12
|
+
when no input files exist (`--domain [ecommerce|banking|saas]`).
|
|
13
|
+
3. Pre-request Scripts: Dynamic request timestamps, UUIDs, nonces, and automated Bearer Auth headers.
|
|
14
14
|
4. Test Scripts (pm.test):
|
|
15
15
|
- Expected HTTP Status assertions.
|
|
16
|
-
-
|
|
16
|
+
- SLA Latency assertions (< 1000ms).
|
|
17
17
|
- Response Content-Type JSON header validation.
|
|
18
|
-
- Autonomous Token & ID Extractor
|
|
19
|
-
|
|
20
|
-
5. Companion Environment Generator: Outputs `postman_environment.json` with base URLs.
|
|
21
|
-
6.
|
|
18
|
+
- Autonomous Token & ID Extractor (`pm.environment.set('bearerToken', ...)`).
|
|
19
|
+
- Native AJV JSON Schema Validation (`pm.response.to.have.jsonSchema(schema)`).
|
|
20
|
+
5. Companion Environment Generator: Outputs `postman_environment.json` with base URLs and variables.
|
|
21
|
+
6. Data-Driven Testing (DDT) Suite: Outputs companion `test_data.csv` for Newman matrix execution.
|
|
22
|
+
7. 100% UTF-8 No BOM, portable, Newman CLI and CI/CD compatible.
|
|
22
23
|
|
|
23
24
|
Usage:
|
|
24
|
-
|
|
25
|
+
# 1. From existing OpenAPI / Swagger
|
|
26
|
+
python tools/export_postman_collection.py --spec swagger.json -o collection.json --env-output env.json
|
|
27
|
+
|
|
28
|
+
# 2. Zero-Input Autonomous Generation (No input files!)
|
|
29
|
+
python tools/export_postman_collection.py --domain ecommerce -o ecom_collection.json --ddt-output ecom_data.csv
|
|
30
|
+
python tools/export_postman_collection.py --domain banking -o banking_collection.json --env-output banking_env.json
|
|
31
|
+
|
|
32
|
+
# 3. From browser network trace (.har)
|
|
25
33
|
python tools/export_postman_collection.py --har traffic.har -o collection.json
|
|
26
|
-
python tools/export_postman_collection.py --sample -o sample_collection.json --env-output sample_env.json
|
|
27
34
|
"""
|
|
28
35
|
|
|
29
36
|
import os
|
|
30
37
|
import sys
|
|
31
38
|
import json
|
|
32
39
|
import uuid
|
|
40
|
+
import csv
|
|
33
41
|
import argparse
|
|
34
42
|
from typing import Dict, Any, List, Optional
|
|
35
43
|
from urllib.parse import urlparse, parse_qs
|
|
@@ -50,10 +58,11 @@ def generate_uuid() -> str:
|
|
|
50
58
|
def build_pre_request_script() -> List[str]:
|
|
51
59
|
"""Generates standard Postman Pre-request script lines."""
|
|
52
60
|
return [
|
|
53
|
-
"// Auto-generated Pre-request Script by
|
|
54
|
-
"// 1. Dynamic Timestamp &
|
|
61
|
+
"// Auto-generated Pre-request Script by Synapse-Hub",
|
|
62
|
+
"// 1. Dynamic Timestamp & Correlation ID generation",
|
|
55
63
|
"pm.variables.set('req_timestamp', new Date().toISOString());",
|
|
56
64
|
"pm.variables.set('req_nonce', Math.random().toString(36).substring(2, 12));",
|
|
65
|
+
"pm.variables.set('correlation_id', 'CORR-' + pm.variables.get('req_nonce'));",
|
|
57
66
|
"",
|
|
58
67
|
"// 2. Automated Bearer Token Injection if present in environment",
|
|
59
68
|
"var token = pm.environment.get('bearerToken') || pm.collectionVariables.get('bearerToken');",
|
|
@@ -63,10 +72,14 @@ def build_pre_request_script() -> List[str]:
|
|
|
63
72
|
]
|
|
64
73
|
|
|
65
74
|
|
|
66
|
-
def build_test_script(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
def build_test_script(
|
|
76
|
+
expected_status: int = 200,
|
|
77
|
+
max_time_ms: int = 1000,
|
|
78
|
+
response_schema: Optional[Dict[str, Any]] = None
|
|
79
|
+
) -> List[str]:
|
|
80
|
+
"""Generates comprehensive Postman Test script lines with AJV JSON Schema assertion."""
|
|
81
|
+
lines = [
|
|
82
|
+
"// Auto-generated Test Assertions by Synapse-Hub",
|
|
70
83
|
f"// 1. Assert Status Code is {expected_status}",
|
|
71
84
|
f"pm.test('Status code is {expected_status}', function () {{",
|
|
72
85
|
f" pm.expect(pm.response.code).to.be.oneOf([{expected_status}, 200, 201]);",
|
|
@@ -93,12 +106,13 @@ def build_test_script(expected_status: int = 200, max_time_ms: int = 1000) -> Li
|
|
|
93
106
|
" if (authToken) {",
|
|
94
107
|
" pm.environment.set('bearerToken', authToken);",
|
|
95
108
|
" pm.collectionVariables.set('bearerToken', authToken);",
|
|
96
|
-
" console.log('[
|
|
109
|
+
" console.log('[Synapse-Hub] Captured dynamic bearerToken: ' + authToken.substring(0, 15) + '...');",
|
|
97
110
|
" }",
|
|
98
111
|
" // Auto-extract newly created ID",
|
|
99
112
|
" var entityId = jsonBody.id || jsonBody.code || (jsonBody.data && (jsonBody.data.id || jsonBody.data.code));",
|
|
100
113
|
" if (entityId) {",
|
|
101
114
|
" pm.environment.set('lastCreatedId', String(entityId));",
|
|
115
|
+
" console.log('[Synapse-Hub] Captured lastCreatedId: ' + entityId);",
|
|
102
116
|
" }",
|
|
103
117
|
" } catch (e) {",
|
|
104
118
|
" // Response is non-JSON or empty body",
|
|
@@ -106,6 +120,20 @@ def build_test_script(expected_status: int = 200, max_time_ms: int = 1000) -> Li
|
|
|
106
120
|
"}"
|
|
107
121
|
]
|
|
108
122
|
|
|
123
|
+
# 5. AJV Native JSON Schema Validation
|
|
124
|
+
if response_schema:
|
|
125
|
+
schema_clean = json.dumps(response_schema, ensure_ascii=False)
|
|
126
|
+
lines.extend([
|
|
127
|
+
"",
|
|
128
|
+
"// 5. AJV JSON Schema Contract Validation",
|
|
129
|
+
"pm.test('Response conforms to OpenAPI JSON Schema', function () {",
|
|
130
|
+
f" var schema = {schema_clean};",
|
|
131
|
+
" pm.response.to.have.jsonSchema(schema);",
|
|
132
|
+
"});"
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
return lines
|
|
136
|
+
|
|
109
137
|
|
|
110
138
|
def mock_schema_payload(schema: Dict[str, Any], depth: int = 0) -> Any:
|
|
111
139
|
"""Recursively generates realistic mock JSON payload from OpenAPI schema."""
|
|
@@ -151,7 +179,6 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
151
179
|
col_desc = info_dict.get("description", "Auto-generated Postman Collection from OpenAPI / Swagger")
|
|
152
180
|
|
|
153
181
|
# Determine Base URL
|
|
154
|
-
base_url = "{{baseUrl}}"
|
|
155
182
|
if base_url_override:
|
|
156
183
|
base_url_default = base_url_override
|
|
157
184
|
elif "servers" in spec_data and spec_data["servers"]:
|
|
@@ -180,6 +207,11 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
180
207
|
"key": "bearerToken",
|
|
181
208
|
"value": "",
|
|
182
209
|
"type": "string"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"key": "lastCreatedId",
|
|
213
|
+
"value": "1",
|
|
214
|
+
"type": "string"
|
|
183
215
|
}
|
|
184
216
|
],
|
|
185
217
|
"item": []
|
|
@@ -189,49 +221,45 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
189
221
|
paths = spec_data.get("paths", {})
|
|
190
222
|
|
|
191
223
|
for path, path_item in paths.items():
|
|
192
|
-
for method in ("get", "post", "put", "patch", "delete"
|
|
224
|
+
for method in ("get", "post", "put", "patch", "delete"):
|
|
193
225
|
if method not in path_item:
|
|
194
226
|
continue
|
|
195
227
|
|
|
196
228
|
op = path_item[method]
|
|
197
229
|
summary = op.get("summary") or f"{method.upper()} {path}"
|
|
198
|
-
tags = op.get("tags"
|
|
199
|
-
tag_name = tags[0]
|
|
230
|
+
tags = op.get("tags", ["Default"])
|
|
231
|
+
tag_name = tags[0] if tags else "Default"
|
|
200
232
|
|
|
201
|
-
#
|
|
233
|
+
# Parse headers
|
|
202
234
|
headers = [
|
|
203
235
|
{"key": "Content-Type", "value": "application/json", "type": "text"},
|
|
204
236
|
{"key": "Accept", "value": "application/json", "type": "text"}
|
|
205
237
|
]
|
|
206
238
|
|
|
207
|
-
# Query Parameters
|
|
239
|
+
# Parse Query Parameters
|
|
208
240
|
query_params = []
|
|
209
|
-
for
|
|
210
|
-
|
|
211
|
-
if p_in == "query":
|
|
241
|
+
for p in op.get("parameters", []):
|
|
242
|
+
if p.get("in") == "query":
|
|
212
243
|
query_params.append({
|
|
213
|
-
"key":
|
|
214
|
-
"value": str(
|
|
215
|
-
"description":
|
|
244
|
+
"key": p.get("name"),
|
|
245
|
+
"value": str(p.get("example", "")),
|
|
246
|
+
"description": p.get("description", "")
|
|
216
247
|
})
|
|
217
|
-
elif
|
|
248
|
+
elif p.get("in") == "header":
|
|
218
249
|
headers.append({
|
|
219
|
-
"key":
|
|
220
|
-
"value": str(
|
|
221
|
-
"type": "text"
|
|
222
|
-
"description": param.get("description", "")
|
|
250
|
+
"key": p.get("name"),
|
|
251
|
+
"value": str(p.get("example", "")),
|
|
252
|
+
"type": "text"
|
|
223
253
|
})
|
|
224
254
|
|
|
225
|
-
# Request Body
|
|
255
|
+
# Parse Request Body
|
|
226
256
|
body_dict = None
|
|
227
257
|
if method in ("post", "put", "patch"):
|
|
228
|
-
# OpenAPI 3.0 requestBody
|
|
229
258
|
req_body = op.get("requestBody", {})
|
|
230
259
|
content = req_body.get("content", {})
|
|
231
260
|
json_content = content.get("application/json", {})
|
|
232
261
|
schema = json_content.get("schema")
|
|
233
262
|
|
|
234
|
-
# Swagger 2.0 body parameter fallback
|
|
235
263
|
if not schema:
|
|
236
264
|
for p in op.get("parameters", []):
|
|
237
265
|
if p.get("in") == "body":
|
|
@@ -262,7 +290,7 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
262
290
|
if query_params:
|
|
263
291
|
url_obj["query"] = query_params
|
|
264
292
|
|
|
265
|
-
# Status code for assertion
|
|
293
|
+
# Status code and response schema for assertion
|
|
266
294
|
responses = op.get("responses", {})
|
|
267
295
|
expected_status = 200
|
|
268
296
|
for code_str in responses.keys():
|
|
@@ -270,6 +298,11 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
270
298
|
expected_status = int(code_str)
|
|
271
299
|
break
|
|
272
300
|
|
|
301
|
+
resp_info = responses.get(str(expected_status), {}) or responses.get("200", {}) or responses.get("201", {})
|
|
302
|
+
resp_schema = resp_info.get("content", {}).get("application/json", {}).get("schema")
|
|
303
|
+
if not resp_schema and "schema" in resp_info:
|
|
304
|
+
resp_schema = resp_info["schema"]
|
|
305
|
+
|
|
273
306
|
postman_request = {
|
|
274
307
|
"name": summary,
|
|
275
308
|
"event": [
|
|
@@ -284,7 +317,10 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
284
317
|
"listen": "test",
|
|
285
318
|
"script": {
|
|
286
319
|
"type": "text/javascript",
|
|
287
|
-
"exec": build_test_script(
|
|
320
|
+
"exec": build_test_script(
|
|
321
|
+
expected_status=expected_status,
|
|
322
|
+
response_schema=resp_schema
|
|
323
|
+
)
|
|
288
324
|
}
|
|
289
325
|
}
|
|
290
326
|
],
|
|
@@ -304,11 +340,12 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
304
340
|
folders[tag_name] = []
|
|
305
341
|
folders[tag_name].append(postman_request)
|
|
306
342
|
|
|
307
|
-
#
|
|
308
|
-
for folder_name,
|
|
343
|
+
# Nest grouped requests into folder items
|
|
344
|
+
for folder_name, req_list in folders.items():
|
|
309
345
|
collection["item"].append({
|
|
310
346
|
"name": folder_name,
|
|
311
|
-
"item":
|
|
347
|
+
"item": req_list,
|
|
348
|
+
"description": f"Endpoints grouped under {folder_name}"
|
|
312
349
|
})
|
|
313
350
|
|
|
314
351
|
return collection
|
|
@@ -317,38 +354,34 @@ def parse_openapi_spec(spec_data: Dict[str, Any], base_url_override: Optional[st
|
|
|
317
354
|
def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] = None) -> Dict[str, Any]:
|
|
318
355
|
"""Parses HTTP Archive (.har) into a Postman Collection v2.1.0."""
|
|
319
356
|
entries = har_data.get("log", {}).get("entries", [])
|
|
357
|
+
collection_name = "HAR Recorded API Collection"
|
|
358
|
+
|
|
359
|
+
base_url_default = base_url_override or "https://api.staging.enterprise.local"
|
|
360
|
+
|
|
320
361
|
collection = {
|
|
321
362
|
"info": {
|
|
322
363
|
"_postman_id": generate_uuid(),
|
|
323
|
-
"name":
|
|
324
|
-
"description": "
|
|
364
|
+
"name": collection_name,
|
|
365
|
+
"description": "Generated from HTTP Archive Network Trace",
|
|
325
366
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
326
367
|
},
|
|
327
368
|
"variable": [
|
|
328
|
-
{"key": "baseUrl", "value":
|
|
329
|
-
{"key": "bearerToken", "value": "", "type": "string"}
|
|
369
|
+
{"key": "baseUrl", "value": base_url_default, "type": "string"},
|
|
370
|
+
{"key": "bearerToken", "value": "", "type": "string"},
|
|
371
|
+
{"key": "lastCreatedId", "value": "1", "type": "string"}
|
|
330
372
|
],
|
|
331
373
|
"item": []
|
|
332
374
|
}
|
|
333
375
|
|
|
334
376
|
requests_by_host: Dict[str, List[Dict[str, Any]]] = {}
|
|
335
377
|
|
|
336
|
-
for
|
|
337
|
-
req =
|
|
338
|
-
resp =
|
|
339
|
-
|
|
378
|
+
for idx, e in enumerate(entries, 1):
|
|
379
|
+
req = e.get("request", {})
|
|
380
|
+
resp = e.get("response", {})
|
|
381
|
+
raw_url = req.get("url", "/")
|
|
340
382
|
method = req.get("method", "GET").upper()
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# Filter static files
|
|
344
|
-
if any(parsed_url.path.lower().endswith(ext) for ext in (".png", ".jpg", ".css", ".js", ".woff2", ".ico")):
|
|
345
|
-
continue
|
|
346
|
-
|
|
347
|
-
host = parsed_url.netloc or "DefaultHost"
|
|
348
|
-
path = parsed_url.path
|
|
349
|
-
query = parse_qs(parsed_url.query)
|
|
350
|
-
|
|
351
|
-
query_list = [{"key": k, "value": v[0] if v else ""} for k, v in query.items()]
|
|
383
|
+
p = urlparse(raw_url)
|
|
384
|
+
host = p.netloc or "Default"
|
|
352
385
|
|
|
353
386
|
headers = []
|
|
354
387
|
for h in req.get("headers", []):
|
|
@@ -356,19 +389,25 @@ def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] =
|
|
|
356
389
|
if name.lower() not in ("cookie", "host", "content-length"):
|
|
357
390
|
headers.append({"key": name, "value": h.get("value", ""), "type": "text"})
|
|
358
391
|
|
|
392
|
+
path_segments = [seg for seg in p.path.strip("/").split("/") if seg]
|
|
393
|
+
url_obj = {
|
|
394
|
+
"raw": f"{{{{baseUrl}}}}{p.path}" + (f"?{p.query}" if p.query else ""),
|
|
395
|
+
"host": ["{{baseUrl}}"],
|
|
396
|
+
"path": path_segments
|
|
397
|
+
}
|
|
398
|
+
|
|
359
399
|
body_dict = None
|
|
360
|
-
post_data = req.get("postData")
|
|
361
|
-
if post_data
|
|
400
|
+
post_data = req.get("postData", {})
|
|
401
|
+
if post_data.get("text"):
|
|
362
402
|
body_dict = {
|
|
363
403
|
"mode": "raw",
|
|
364
|
-
"raw": post_data
|
|
404
|
+
"raw": post_data["text"],
|
|
365
405
|
"options": {"raw": {"language": "json"}}
|
|
366
406
|
}
|
|
367
407
|
|
|
368
|
-
|
|
369
|
-
|
|
408
|
+
expected_status = resp.get("status", 200) or 200
|
|
370
409
|
postman_item = {
|
|
371
|
-
"name": f"{method} {path}",
|
|
410
|
+
"name": f"[{method}] {p.path or '/'}",
|
|
372
411
|
"event": [
|
|
373
412
|
{
|
|
374
413
|
"listen": "prerequest",
|
|
@@ -376,21 +415,18 @@ def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] =
|
|
|
376
415
|
},
|
|
377
416
|
{
|
|
378
417
|
"listen": "test",
|
|
379
|
-
"script": {"type": "text/javascript", "exec": build_test_script(expected_status=
|
|
418
|
+
"script": {"type": "text/javascript", "exec": build_test_script(expected_status=expected_status)}
|
|
380
419
|
}
|
|
381
420
|
],
|
|
382
421
|
"request": {
|
|
383
422
|
"method": method,
|
|
384
423
|
"header": headers,
|
|
385
|
-
"url":
|
|
386
|
-
|
|
387
|
-
"host": [parsed_url.scheme + "://" + parsed_url.netloc],
|
|
388
|
-
"path": [p for p in path.strip("/").split("/") if p],
|
|
389
|
-
"query": query_list
|
|
390
|
-
}
|
|
424
|
+
"url": url_obj,
|
|
425
|
+
"description": f"Recorded step {idx}"
|
|
391
426
|
},
|
|
392
427
|
"response": []
|
|
393
428
|
}
|
|
429
|
+
|
|
394
430
|
if body_dict:
|
|
395
431
|
postman_item["request"]["body"] = body_dict
|
|
396
432
|
|
|
@@ -398,8 +434,12 @@ def parse_har_file(har_data: Dict[str, Any], base_url_override: Optional[str] =
|
|
|
398
434
|
requests_by_host[host] = []
|
|
399
435
|
requests_by_host[host].append(postman_item)
|
|
400
436
|
|
|
401
|
-
for
|
|
402
|
-
collection["item"].append({
|
|
437
|
+
for host, reqs in requests_by_host.items():
|
|
438
|
+
collection["item"].append({
|
|
439
|
+
"name": f"Host: {host}",
|
|
440
|
+
"item": reqs,
|
|
441
|
+
"description": f"Recorded requests against {host}"
|
|
442
|
+
})
|
|
403
443
|
|
|
404
444
|
return collection
|
|
405
445
|
|
|
@@ -410,53 +450,178 @@ def generate_postman_environment(name: str = "Enterprise-Staging-Env", base_url:
|
|
|
410
450
|
"id": generate_uuid(),
|
|
411
451
|
"name": name,
|
|
412
452
|
"values": [
|
|
413
|
-
{"key": "baseUrl", "value": base_url, "
|
|
414
|
-
{"key": "bearerToken", "value": "", "
|
|
415
|
-
{"key": "lastCreatedId", "value": "", "
|
|
453
|
+
{"key": "baseUrl", "value": base_url, "enabled": True},
|
|
454
|
+
{"key": "bearerToken", "value": "", "enabled": True},
|
|
455
|
+
{"key": "lastCreatedId", "value": "1", "enabled": True},
|
|
456
|
+
{"key": "adminUser", "value": "qa_admin@enterprise.local", "enabled": True},
|
|
457
|
+
{"key": "adminPass", "value": "SecretVault#2026", "enabled": True}
|
|
416
458
|
],
|
|
417
459
|
"_postman_variable_scope": "environment"
|
|
418
460
|
}
|
|
419
461
|
|
|
420
462
|
|
|
421
|
-
def
|
|
422
|
-
"""
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
"
|
|
427
|
-
"
|
|
428
|
-
"
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
463
|
+
def generate_ddt_dataset(domain: str = "ecommerce") -> List[Dict[str, Any]]:
|
|
464
|
+
"""Generates companion Data-Driven Testing (DDT) matrix for Newman execution."""
|
|
465
|
+
if domain == "banking":
|
|
466
|
+
return [
|
|
467
|
+
{"scenario": "Valid Domestic Transfer", "sender": "ACC-101", "recipient": "ACC-902", "amount": 500000, "expectedStatus": 201},
|
|
468
|
+
{"scenario": "Zero Amount Boundary", "sender": "ACC-101", "recipient": "ACC-902", "amount": 0, "expectedStatus": 400},
|
|
469
|
+
{"scenario": "Negative Amount Attack", "sender": "ACC-101", "recipient": "ACC-902", "amount": -150000, "expectedStatus": 400},
|
|
470
|
+
{"scenario": "Exceed Daily Balance Limit", "sender": "ACC-101", "recipient": "ACC-902", "amount": 999999999, "expectedStatus": 422},
|
|
471
|
+
{"scenario": "Invalid Recipient Format", "sender": "ACC-101", "recipient": "<script>alert(1)</script>", "expectedStatus": 400}
|
|
472
|
+
]
|
|
473
|
+
elif domain == "saas":
|
|
474
|
+
return [
|
|
475
|
+
{"scenario": "Standard Member Creation", "username": "member_valid", "role": "MEMBER", "priority": 10, "expectedStatus": 201},
|
|
476
|
+
{"scenario": "Admin Role Assignment", "username": "admin_chief", "role": "ADMIN", "priority": 1, "expectedStatus": 201},
|
|
477
|
+
{"scenario": "Empty Username Boundary", "username": "", "role": "MEMBER", "priority": 10, "expectedStatus": 400},
|
|
478
|
+
{"scenario": "SQLi Injection Probe", "username": "' OR '1'='1' --", "role": "MEMBER", "priority": 10, "expectedStatus": 400},
|
|
479
|
+
{"scenario": "Invalid Role Privilege", "username": "intruder_user", "role": "SUPER_ROOT_UNAUTHORIZED", "priority": 99, "expectedStatus": 403}
|
|
480
|
+
]
|
|
481
|
+
else: # ecommerce default
|
|
482
|
+
return [
|
|
483
|
+
{"scenario": "Valid Catalog Creation", "code": "SKU_AUTO_2026", "name": "Enterprise Quantum Server", "priority": 10, "expectedStatus": 201},
|
|
484
|
+
{"scenario": "Short Code Boundary (< 3 chars)", "code": "A1", "name": "Invalid Item", "priority": 5, "expectedStatus": 400},
|
|
485
|
+
{"scenario": "Max Length Code Boundary", "code": "SKU_MAX_CHARS_990000", "name": "Max Length Product", "priority": 50, "expectedStatus": 201},
|
|
486
|
+
{"scenario": "Empty Product Name", "code": "SKU_VALID_02", "name": "", "priority": 20, "expectedStatus": 400},
|
|
487
|
+
{"scenario": "XSS Script Body Injection", "code": "SKU_XSS_03", "name": "<script>alert('pwned')</script>", "priority": 1, "expectedStatus": 400}
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def get_domain_spec(domain: str = "ecommerce") -> Dict[str, Any]:
|
|
492
|
+
"""Autonomous Zero-Input Domain Spec Synthesizer."""
|
|
493
|
+
if domain == "banking":
|
|
494
|
+
return {
|
|
495
|
+
"openapi": "3.0.3",
|
|
496
|
+
"info": {
|
|
497
|
+
"title": "Enterprise Banking & Fund Transfer API",
|
|
498
|
+
"version": "2.4.0",
|
|
499
|
+
"description": "Production Banking API with OAuth2, Idempotent Transfers, and Audit Queries"
|
|
500
|
+
},
|
|
501
|
+
"servers": [{"url": "https://api.banking.staging.local"}],
|
|
502
|
+
"paths": {
|
|
503
|
+
"/api/v1/auth/token": {
|
|
504
|
+
"post": {
|
|
505
|
+
"tags": ["Authentication"],
|
|
506
|
+
"summary": "Obtain Bearer JWT Token",
|
|
507
|
+
"requestBody": {
|
|
508
|
+
"content": {
|
|
509
|
+
"application/json": {
|
|
510
|
+
"schema": {
|
|
511
|
+
"type": "object",
|
|
512
|
+
"required": ["clientId", "clientSecret"],
|
|
513
|
+
"properties": {
|
|
514
|
+
"clientId": {"type": "string", "example": "bank_admin"},
|
|
515
|
+
"clientSecret": {"type": "string", "example": "SecretVault#2026"}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
"responses": {
|
|
522
|
+
"200": {
|
|
523
|
+
"description": "Token granted",
|
|
524
|
+
"content": {
|
|
525
|
+
"application/json": {
|
|
526
|
+
"schema": {
|
|
527
|
+
"type": "object",
|
|
528
|
+
"required": ["token", "tokenType", "expiresIn"],
|
|
529
|
+
"properties": {
|
|
530
|
+
"token": {"type": "string", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.fake"},
|
|
531
|
+
"tokenType": {"type": "string", "example": "Bearer"},
|
|
532
|
+
"expiresIn": {"type": "integer", "example": 3600}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
445
535
|
}
|
|
446
536
|
}
|
|
447
537
|
}
|
|
448
538
|
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
"/api/v1/accounts/me": {
|
|
542
|
+
"get": {
|
|
543
|
+
"tags": ["Account Management"],
|
|
544
|
+
"summary": "Get Account Balance & Profile",
|
|
545
|
+
"responses": {
|
|
546
|
+
"200": {
|
|
547
|
+
"description": "Account details retrieved",
|
|
548
|
+
"content": {
|
|
549
|
+
"application/json": {
|
|
550
|
+
"schema": {
|
|
551
|
+
"type": "object",
|
|
552
|
+
"required": ["accountNumber", "balance", "currency"],
|
|
553
|
+
"properties": {
|
|
554
|
+
"accountNumber": {"type": "string", "example": "ACC-889922"},
|
|
555
|
+
"balance": {"type": "number", "example": 25000000.0},
|
|
556
|
+
"currency": {"type": "string", "example": "VND"}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
"/api/v1/transfers": {
|
|
566
|
+
"post": {
|
|
567
|
+
"tags": ["Fund Transfers"],
|
|
568
|
+
"summary": "Execute Idempotent Fund Transfer",
|
|
569
|
+
"requestBody": {
|
|
453
570
|
"content": {
|
|
454
571
|
"application/json": {
|
|
455
572
|
"schema": {
|
|
456
573
|
"type": "object",
|
|
574
|
+
"required": ["recipientAccount", "amount", "idempotencyKey"],
|
|
457
575
|
"properties": {
|
|
458
|
-
"
|
|
459
|
-
"
|
|
576
|
+
"recipientAccount": {"type": "string", "example": "ACC-990011"},
|
|
577
|
+
"amount": {"type": "number", "example": 500000.0},
|
|
578
|
+
"idempotencyKey": {"type": "string", "example": "{{$guid}}"},
|
|
579
|
+
"remark": {"type": "string", "example": "Payment for invoice"}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
"responses": {
|
|
586
|
+
"201": {
|
|
587
|
+
"description": "Transfer queued or completed",
|
|
588
|
+
"content": {
|
|
589
|
+
"application/json": {
|
|
590
|
+
"schema": {
|
|
591
|
+
"type": "object",
|
|
592
|
+
"required": ["id", "status", "transactionRef"],
|
|
593
|
+
"properties": {
|
|
594
|
+
"id": {"type": "string", "example": "TX-998822"},
|
|
595
|
+
"status": {"type": "string", "example": "COMPLETED"},
|
|
596
|
+
"transactionRef": {"type": "string", "example": "REF-AUTO-2026"}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
"/api/v1/transfers/{id}": {
|
|
606
|
+
"get": {
|
|
607
|
+
"tags": ["Fund Transfers"],
|
|
608
|
+
"summary": "Audit Transfer Status by ID",
|
|
609
|
+
"parameters": [
|
|
610
|
+
{"name": "id", "in": "path", "required": True, "example": "{{lastCreatedId}}"}
|
|
611
|
+
],
|
|
612
|
+
"responses": {
|
|
613
|
+
"200": {
|
|
614
|
+
"description": "Transfer status verified",
|
|
615
|
+
"content": {
|
|
616
|
+
"application/json": {
|
|
617
|
+
"schema": {
|
|
618
|
+
"type": "object",
|
|
619
|
+
"required": ["id", "status", "amount"],
|
|
620
|
+
"properties": {
|
|
621
|
+
"id": {"type": "string", "example": "TX-998822"},
|
|
622
|
+
"status": {"type": "string", "example": "COMPLETED"},
|
|
623
|
+
"amount": {"type": "number", "example": 500000.0}
|
|
624
|
+
}
|
|
460
625
|
}
|
|
461
626
|
}
|
|
462
627
|
}
|
|
@@ -464,142 +629,294 @@ def get_sample_spec() -> Dict[str, Any]:
|
|
|
464
629
|
}
|
|
465
630
|
}
|
|
466
631
|
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
elif domain == "saas":
|
|
635
|
+
return {
|
|
636
|
+
"openapi": "3.0.3",
|
|
637
|
+
"info": {
|
|
638
|
+
"title": "Enterprise SaaS Tenant & User RBAC API",
|
|
639
|
+
"version": "1.8.0",
|
|
640
|
+
"description": "Multi-Tenant SaaS Management Suite"
|
|
467
641
|
},
|
|
468
|
-
"
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
"
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
642
|
+
"servers": [{"url": "https://api.saas.staging.local"}],
|
|
643
|
+
"paths": {
|
|
644
|
+
"/api/v1/auth/login": {
|
|
645
|
+
"post": {
|
|
646
|
+
"tags": ["Identity"],
|
|
647
|
+
"summary": "Tenant Admin Login",
|
|
648
|
+
"requestBody": {
|
|
649
|
+
"content": {
|
|
650
|
+
"application/json": {
|
|
651
|
+
"schema": {
|
|
652
|
+
"type": "object",
|
|
653
|
+
"required": ["tenantId", "username", "password"],
|
|
654
|
+
"properties": {
|
|
655
|
+
"tenantId": {"type": "string", "example": "tenant_enterprise"},
|
|
656
|
+
"username": {"type": "string", "example": "admin"},
|
|
657
|
+
"password": {"type": "string", "example": "SecretPass#2026"}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
"responses": {
|
|
664
|
+
"200": {
|
|
665
|
+
"description": "Login successful",
|
|
666
|
+
"content": {
|
|
667
|
+
"application/json": {
|
|
668
|
+
"schema": {
|
|
669
|
+
"type": "object",
|
|
670
|
+
"required": ["token", "role"],
|
|
671
|
+
"properties": {
|
|
672
|
+
"token": {"type": "string", "example": "jwt.tenant.token"},
|
|
673
|
+
"role": {"type": "string", "example": "TENANT_ADMIN"}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
491
676
|
}
|
|
492
677
|
}
|
|
493
678
|
}
|
|
494
679
|
}
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
"/api/v1/users": {
|
|
683
|
+
"get": {
|
|
684
|
+
"tags": ["User Directory"],
|
|
685
|
+
"summary": "List Active Tenant Users",
|
|
686
|
+
"responses": {"200": {"description": "Users list"}}
|
|
495
687
|
},
|
|
496
|
-
"
|
|
688
|
+
"post": {
|
|
689
|
+
"tags": ["User Directory"],
|
|
690
|
+
"summary": "Provision New Tenant Member",
|
|
691
|
+
"requestBody": {
|
|
692
|
+
"content": {
|
|
693
|
+
"application/json": {
|
|
694
|
+
"schema": {
|
|
695
|
+
"type": "object",
|
|
696
|
+
"required": ["email", "role"],
|
|
697
|
+
"properties": {
|
|
698
|
+
"email": {"type": "string", "example": "user_auto@tenant.com"},
|
|
699
|
+
"role": {"type": "string", "example": "MEMBER"}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
"responses": {
|
|
706
|
+
"201": {
|
|
707
|
+
"description": "User created",
|
|
708
|
+
"content": {
|
|
709
|
+
"application/json": {
|
|
710
|
+
"schema": {
|
|
711
|
+
"type": "object",
|
|
712
|
+
"required": ["id", "email"],
|
|
713
|
+
"properties": {
|
|
714
|
+
"id": {"type": "string", "example": "USR-101"},
|
|
715
|
+
"email": {"type": "string", "example": "user_auto@tenant.com"}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
"/api/v1/users/{id}": {
|
|
725
|
+
"delete": {
|
|
726
|
+
"tags": ["User Directory"],
|
|
727
|
+
"summary": "Deprovision Member (Teardown Cleanup)",
|
|
728
|
+
"parameters": [{"name": "id", "in": "path", "required": True, "example": "{{lastCreatedId}}"}],
|
|
729
|
+
"responses": {"204": {"description": "User deprovisioned"}}
|
|
730
|
+
}
|
|
497
731
|
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else: # E-commerce default
|
|
735
|
+
return {
|
|
736
|
+
"openapi": "3.0.3",
|
|
737
|
+
"info": {
|
|
738
|
+
"title": "Enterprise E-Commerce Core API",
|
|
739
|
+
"version": "3.2.0",
|
|
740
|
+
"description": "Enterprise E-Commerce Commerce Engine API with Auth, Search, Cart, Order, and Teardown"
|
|
498
741
|
},
|
|
499
|
-
"
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
"
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
742
|
+
"servers": [{"url": "https://api.staging.enterprise.local"}],
|
|
743
|
+
"paths": {
|
|
744
|
+
"/api/v1/auth/login": {
|
|
745
|
+
"post": {
|
|
746
|
+
"tags": ["Authentication"],
|
|
747
|
+
"summary": "User Authentication (Login)",
|
|
748
|
+
"requestBody": {
|
|
749
|
+
"content": {
|
|
750
|
+
"application/json": {
|
|
751
|
+
"schema": {
|
|
752
|
+
"type": "object",
|
|
753
|
+
"required": ["username", "password"],
|
|
754
|
+
"properties": {
|
|
755
|
+
"username": {"type": "string", "example": "admin"},
|
|
756
|
+
"password": {"type": "string", "example": "SecretVault#2026"}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
"responses": {
|
|
763
|
+
"200": {
|
|
764
|
+
"description": "Successful Login",
|
|
765
|
+
"content": {
|
|
766
|
+
"application/json": {
|
|
767
|
+
"schema": {
|
|
768
|
+
"type": "object",
|
|
769
|
+
"required": ["token", "tokenType"],
|
|
770
|
+
"properties": {
|
|
771
|
+
"token": {"type": "string", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.fake"},
|
|
772
|
+
"tokenType": {"type": "string", "example": "Bearer"}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
"/api/v1/catalogs": {
|
|
782
|
+
"get": {
|
|
783
|
+
"tags": ["Catalog Service"],
|
|
784
|
+
"summary": "Search Product Catalogs",
|
|
785
|
+
"parameters": [
|
|
786
|
+
{"name": "query", "in": "query", "example": "server"},
|
|
787
|
+
{"name": "limit", "in": "query", "example": "20"}
|
|
788
|
+
],
|
|
789
|
+
"responses": {
|
|
790
|
+
"200": {
|
|
791
|
+
"description": "Catalogs search result",
|
|
792
|
+
"content": {
|
|
793
|
+
"application/json": {
|
|
794
|
+
"schema": {
|
|
795
|
+
"type": "object",
|
|
796
|
+
"required": ["items", "total"],
|
|
797
|
+
"properties": {
|
|
798
|
+
"items": {"type": "array", "items": {"type": "object"}},
|
|
799
|
+
"total": {"type": "integer", "example": 42}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
"post": {
|
|
808
|
+
"tags": ["Catalog Service"],
|
|
809
|
+
"summary": "Create Product Item",
|
|
810
|
+
"requestBody": {
|
|
811
|
+
"content": {
|
|
812
|
+
"application/json": {
|
|
813
|
+
"schema": {
|
|
814
|
+
"type": "object",
|
|
815
|
+
"required": ["code", "name"],
|
|
816
|
+
"properties": {
|
|
817
|
+
"code": {"type": "string", "example": "SKU_AUTO_2026"},
|
|
818
|
+
"name": {"type": "string", "example": "Enterprise Quantum Server"},
|
|
819
|
+
"priority": {"type": "integer", "example": 10}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
"responses": {
|
|
826
|
+
"201": {
|
|
827
|
+
"description": "Product created",
|
|
828
|
+
"content": {
|
|
829
|
+
"application/json": {
|
|
830
|
+
"schema": {
|
|
831
|
+
"type": "object",
|
|
832
|
+
"required": ["id", "code"],
|
|
833
|
+
"properties": {
|
|
834
|
+
"id": {"type": "string", "example": "PRD-998811"},
|
|
835
|
+
"code": {"type": "string", "example": "SKU_AUTO_2026"}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
"/api/v1/catalogs/{id}": {
|
|
845
|
+
"delete": {
|
|
846
|
+
"tags": ["Catalog Service"],
|
|
847
|
+
"summary": "Delete Catalog Item (Idempotent Teardown)",
|
|
848
|
+
"parameters": [
|
|
849
|
+
{"name": "id", "in": "path", "required": True, "example": "{{lastCreatedId}}"}
|
|
850
|
+
],
|
|
851
|
+
"responses": {"204": {"description": "Item deleted"}}
|
|
852
|
+
}
|
|
507
853
|
}
|
|
508
854
|
}
|
|
509
855
|
}
|
|
510
|
-
}
|
|
511
856
|
|
|
512
857
|
|
|
513
858
|
def main():
|
|
514
859
|
parser = argparse.ArgumentParser(
|
|
515
|
-
description="Export OpenAPI or
|
|
860
|
+
description="Export OpenAPI, HAR, or Zero-Input Domain Synthesis to Postman Collection v2.1.0."
|
|
516
861
|
)
|
|
517
862
|
parser.add_argument("--spec", "-s", help="Path to OpenAPI / Swagger JSON specification file.")
|
|
518
863
|
parser.add_argument("--har", help="Path to HTTP Archive (.har) recording file.")
|
|
864
|
+
parser.add_argument("--domain", choices=["ecommerce", "banking", "saas"], default="ecommerce", help="Zero-input domain synthesizer template. Default: ecommerce.")
|
|
519
865
|
parser.add_argument("--base-url", "-b", help="Override base URL for {{baseUrl}} parameter.")
|
|
520
866
|
parser.add_argument("--output", "-o", help="Target output Postman Collection file path (.json).")
|
|
521
867
|
parser.add_argument("--env-output", help="Optional companion Postman Environment output file (.json).")
|
|
522
|
-
parser.add_argument("--
|
|
523
|
-
parser.add_argument("--
|
|
524
|
-
parser.add_argument("--newman", action="store_true", help="Automatically trigger headless Newman execution on the generated collection.")
|
|
868
|
+
parser.add_argument("--ddt-output", help="Optional companion Data-Driven Testing (DDT) CSV file for Newman.")
|
|
869
|
+
parser.add_argument("--sample", action="store_true", help="Generate sample collection using domain synthesizer.")
|
|
525
870
|
|
|
526
871
|
args = parser.parse_args()
|
|
527
872
|
|
|
528
|
-
#
|
|
529
|
-
if args.
|
|
530
|
-
import subprocess
|
|
531
|
-
import tempfile
|
|
532
|
-
import shutil
|
|
533
|
-
|
|
534
|
-
target_output = args.output or "postman_collection.json"
|
|
535
|
-
spec_path = args.spec
|
|
536
|
-
|
|
537
|
-
# Handle sample spec if no explicit spec file provided
|
|
538
|
-
temp_spec_file = None
|
|
539
|
-
if not spec_path or args.sample:
|
|
540
|
-
fd, temp_spec_file = tempfile.mkstemp(suffix=".json", prefix="sample_spec_")
|
|
541
|
-
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
542
|
-
json.dump(get_sample_spec(), f)
|
|
543
|
-
spec_path = temp_spec_file
|
|
544
|
-
|
|
545
|
-
portman_cmd = ["npx", "--yes", "@apideck/portman", "-l", spec_path, "-o", target_output]
|
|
546
|
-
if args.base_url:
|
|
547
|
-
portman_cmd.extend(["-b", args.base_url])
|
|
548
|
-
if args.newman:
|
|
549
|
-
portman_cmd.extend(["-n"])
|
|
550
|
-
|
|
551
|
-
print(f"⚡ [PORTMAN BRIDGE] Executing: {' '.join(portman_cmd)}")
|
|
552
|
-
try:
|
|
553
|
-
res = subprocess.run(portman_cmd, check=True)
|
|
554
|
-
print(f"✅ [PORTMAN SUCCESS] Collection generated via @apideck/portman: {target_output}")
|
|
555
|
-
if temp_spec_file and os.path.exists(temp_spec_file):
|
|
556
|
-
os.remove(temp_spec_file)
|
|
557
|
-
return
|
|
558
|
-
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
559
|
-
print(f"⚠️ [PORTMAN FALLBACK] Could not run npx portman ({e}). Falling back to internal Pure-Python engine.")
|
|
560
|
-
if temp_spec_file and os.path.exists(temp_spec_file):
|
|
561
|
-
os.remove(temp_spec_file)
|
|
562
|
-
|
|
563
|
-
if args.sample or (not args.spec and not args.har):
|
|
564
|
-
spec_data = get_sample_spec()
|
|
565
|
-
collection = parse_openapi_spec(spec_data, base_url_override=args.base_url)
|
|
566
|
-
elif args.har:
|
|
567
|
-
if not os.path.exists(args.har):
|
|
568
|
-
print(f"❌ Error: HAR file not found: {args.har}", file=sys.stderr)
|
|
569
|
-
sys.exit(1)
|
|
873
|
+
# Determine data source
|
|
874
|
+
if args.har and os.path.exists(args.har):
|
|
570
875
|
with open(args.har, "r", encoding="utf-8") as f:
|
|
571
876
|
har_data = json.load(f)
|
|
572
877
|
collection = parse_har_file(har_data, base_url_override=args.base_url)
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
print(f"❌ Error: OpenAPI specification file not found: {args.spec}", file=sys.stderr)
|
|
576
|
-
sys.exit(1)
|
|
878
|
+
active_domain = "har"
|
|
879
|
+
elif args.spec and os.path.exists(args.spec):
|
|
577
880
|
with open(args.spec, "r", encoding="utf-8") as f:
|
|
578
881
|
spec_data = json.load(f)
|
|
579
882
|
collection = parse_openapi_spec(spec_data, base_url_override=args.base_url)
|
|
883
|
+
active_domain = "spec"
|
|
884
|
+
else:
|
|
885
|
+
# Autonomous Zero-Input Synthesis (User provided no file!)
|
|
886
|
+
spec_data = get_domain_spec(args.domain)
|
|
887
|
+
collection = parse_openapi_spec(spec_data, base_url_override=args.base_url)
|
|
888
|
+
active_domain = args.domain
|
|
889
|
+
print(f"✨ [ZERO-INPUT SYNTHESIS] Synthesized complete '{args.domain.upper()}' Enterprise API Suite from scratch!")
|
|
580
890
|
|
|
581
|
-
|
|
891
|
+
target_output = args.output or f"{active_domain}_collection.json"
|
|
892
|
+
out_dir = os.path.dirname(os.path.abspath(target_output))
|
|
893
|
+
if out_dir and not os.path.exists(out_dir):
|
|
894
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
582
895
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
with open(args.output, "w", encoding="utf-8") as f:
|
|
588
|
-
f.write(out_json)
|
|
589
|
-
print(f"✅ [POSTMAN EXPORT] Successfully created Postman Collection v2.1.0: {args.output}")
|
|
590
|
-
else:
|
|
591
|
-
print(out_json)
|
|
896
|
+
with open(target_output, "w", encoding="utf-8") as f:
|
|
897
|
+
json.dump(collection, f, indent=2, ensure_ascii=False)
|
|
898
|
+
print(f"✅ [POSTMAN EXPORT] Successfully created Postman Collection v2.1.0: {target_output}")
|
|
899
|
+
print(f"🛡️ Embedded: Pre-request Nonce & Auth | SLA Tests (< 1000ms) | AJV JSON Schema Validation | Token Chaining")
|
|
592
900
|
|
|
593
901
|
# Optional Companion Environment
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
902
|
+
env_file = args.env_output or (os.path.splitext(target_output)[0] + "_env.json")
|
|
903
|
+
base_u = args.base_url or "https://api.staging.enterprise.local"
|
|
904
|
+
env_dict = generate_postman_environment(name=f"Synapse-{active_domain.capitalize()}-Env", base_url=base_u)
|
|
905
|
+
with open(env_file, "w", encoding="utf-8") as f:
|
|
906
|
+
json.dump(env_dict, f, indent=2, ensure_ascii=False)
|
|
907
|
+
print(f"🌐 [ENVIRONMENT EXPORT] Successfully created Postman Environment: {env_file}")
|
|
908
|
+
|
|
909
|
+
# Optional Companion Data-Driven Testing (DDT) CSV
|
|
910
|
+
if args.ddt_output or args.sample:
|
|
911
|
+
ddt_file = args.ddt_output or (os.path.splitext(target_output)[0] + "_ddt.csv")
|
|
912
|
+
dataset = generate_ddt_dataset(args.domain if active_domain in ("ecommerce", "banking", "saas") else "ecommerce")
|
|
913
|
+
if dataset:
|
|
914
|
+
with open(ddt_file, "w", encoding="utf-8", newline="") as f:
|
|
915
|
+
writer = csv.DictWriter(f, fieldnames=list(dataset[0].keys()))
|
|
916
|
+
writer.writeheader()
|
|
917
|
+
writer.writerows(dataset)
|
|
918
|
+
print(f"📊 [DDT EXPORT] Successfully created Data-Driven Testing CSV: {ddt_file}")
|
|
919
|
+
print(f"🚀 Execution Command: newman run \"{target_output}\" -e \"{env_file}\" -d \"{ddt_file}\"")
|
|
603
920
|
|
|
604
921
|
|
|
605
922
|
if __name__ == "__main__":
|