feat(yamlreaders): Added PostmanReader for Postman Collection v2.1
Parses Postman collections: - Extracts variables and base_url - Parses nested items and folders - Converts request body and headers - Extracts test scripts for variable extraction
This commit is contained in:
1 parent
0f7dd83b33
commit
d6b97e8b09
1 file changed
+211
@@ -0,0 +1,211 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from models import (
|
||||
AuthModel,
|
||||
ExtractModel,
|
||||
RequestModel,
|
||||
StepModel,
|
||||
TestModel,
|
||||
)
|
||||
|
||||
from .base import BaseReader, ParseError
|
||||
|
||||
|
||||
class PostmanReader(BaseReader):
|
||||
def validate(self, source: str | Path) -> bool:
|
||||
try:
|
||||
data = self.load_yaml(source)
|
||||
|
||||
if "info" not in data:
|
||||
return False
|
||||
|
||||
if "item" not in data:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def parse(self, source: str | Path) -> TestModel:
|
||||
data = self.load_yaml(source)
|
||||
|
||||
info = data.get("info", {})
|
||||
name = info.get("name", "Unnamed Collection")
|
||||
|
||||
variables = self._extract_variables(data)
|
||||
base_url = self._extract_base_url(data)
|
||||
auth = self._extract_auth(data)
|
||||
|
||||
steps = self._parse_items(data.get("item", []))
|
||||
|
||||
return TestModel(
|
||||
name=name,
|
||||
base_url=base_url,
|
||||
variables=variables,
|
||||
auth=auth,
|
||||
steps=steps,
|
||||
)
|
||||
|
||||
def _extract_variables(self, data: dict) -> dict[str, str]:
|
||||
variables = {}
|
||||
|
||||
for var in data.get("variable", []):
|
||||
key = var.get("key", "")
|
||||
value = var.get("value", "")
|
||||
if key:
|
||||
variables[key] = str(value)
|
||||
|
||||
return variables
|
||||
|
||||
def _extract_base_url(self, data: dict) -> str:
|
||||
for var in data.get("variable", []):
|
||||
if var.get("key") == "base_url":
|
||||
return str(var.get("value", ""))
|
||||
|
||||
return ""
|
||||
|
||||
def _extract_auth(self, data: dict) -> AuthModel | None:
|
||||
auth_data = data.get("auth")
|
||||
if not auth_data:
|
||||
return None
|
||||
|
||||
auth_type = auth_data.get("type", "none")
|
||||
credentials = {}
|
||||
|
||||
for item in auth_data.get("basic", auth_data.get("apikey", [])):
|
||||
key = item.get("key", "")
|
||||
value = item.get("value", "")
|
||||
credentials[key] = str(value)
|
||||
|
||||
return AuthModel(type=auth_type, credentials=credentials)
|
||||
|
||||
def _parse_items(self, items: list[dict]) -> list[StepModel]:
|
||||
steps = []
|
||||
|
||||
for item in items:
|
||||
if "item" in item:
|
||||
steps.extend(self._parse_items(item["item"]))
|
||||
elif "request" in item:
|
||||
step = self._parse_item(item)
|
||||
if step:
|
||||
steps.append(step)
|
||||
|
||||
return steps
|
||||
|
||||
def _parse_item(self, item: dict) -> StepModel | None:
|
||||
name = item.get("name", "Unnamed Request")
|
||||
request_data = item.get("request", {})
|
||||
|
||||
if isinstance(request_data, str):
|
||||
request_data = {"method": "GET", "url": request_data}
|
||||
|
||||
request = self._parse_request(request_data)
|
||||
extract = self._parse_events(item.get("event", []))
|
||||
|
||||
return StepModel(
|
||||
name=name,
|
||||
request=request,
|
||||
extract=extract,
|
||||
assert_=[],
|
||||
)
|
||||
|
||||
def _parse_request(self, request: dict) -> RequestModel:
|
||||
url = request.get("url", "")
|
||||
|
||||
if isinstance(url, dict):
|
||||
raw = url.get("raw", "")
|
||||
path_parts = url.get("path", [])
|
||||
url = "/" + "/".join(path_parts) if path_parts else raw
|
||||
|
||||
headers = {}
|
||||
for header in request.get("header", []):
|
||||
key = header.get("key", "")
|
||||
value = header.get("value", "")
|
||||
if key:
|
||||
headers[key] = value
|
||||
|
||||
body = None
|
||||
body_type = "json"
|
||||
body_data = request.get("body", {})
|
||||
|
||||
if body_data:
|
||||
mode = body_data.get("mode", "raw")
|
||||
|
||||
if mode == "raw":
|
||||
raw = body_data.get("raw", "")
|
||||
try:
|
||||
import json
|
||||
body = json.loads(raw)
|
||||
except Exception:
|
||||
body = raw
|
||||
elif mode == "urlencoded":
|
||||
body = {}
|
||||
for item in body_data.get("urlencoded", []):
|
||||
body[item.get("key", "")] = item.get("value", "")
|
||||
elif mode == "formdata":
|
||||
body = {}
|
||||
for item in body_data.get("formdata", []):
|
||||
body[item.get("key", "")] = item.get("value", "")
|
||||
|
||||
return RequestModel(
|
||||
method=request.get("method", "GET").upper(),
|
||||
path=url,
|
||||
headers=headers,
|
||||
body=body,
|
||||
body_type=body_type,
|
||||
)
|
||||
|
||||
def _parse_events(self, events: list[dict]) -> list[ExtractModel]:
|
||||
extract = []
|
||||
|
||||
for event in events:
|
||||
if event.get("listen") == "test":
|
||||
script = event.get("script", {})
|
||||
exec_lines = script.get("exec", [])
|
||||
|
||||
for line in exec_lines:
|
||||
extracted = self._parse_test_script(line)
|
||||
if extracted:
|
||||
extract.extend(extracted)
|
||||
|
||||
return extract
|
||||
|
||||
def _parse_test_script(self, line: str) -> list[ExtractModel] | None:
|
||||
import re
|
||||
|
||||
patterns = [
|
||||
r"pm\.environment\.set\(['\"](\w+)['\"],\s*(.+?)\)",
|
||||
r"pm\.variables\.set\(['\"](\w+)['\"],\s*(.+?)\)",
|
||||
r"pm\.collectionVariables\.set\(['\"](\w+)['\"],\s*(.+?)\)",
|
||||
]
|
||||
|
||||
result = []
|
||||
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, line)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
json_path = match.group(2).strip()
|
||||
|
||||
if json_path.startswith("pm."):
|
||||
json_path = self._convert_pm_to_json_path(json_path)
|
||||
|
||||
result.append(ExtractModel(
|
||||
name=name,
|
||||
json_path=json_path,
|
||||
source="body",
|
||||
))
|
||||
|
||||
return result if result else None
|
||||
|
||||
def _convert_pm_to_json_path(self, pm_expr: str) -> str:
|
||||
import re
|
||||
|
||||
match = re.match(r"pm\.response\.json\(\)\.(\w+)", pm_expr)
|
||||
if match:
|
||||
return f"$.{match.group(1)}"
|
||||
|
||||
return pm_expr
|
||||
Reference in new issue
Block a user