← Files Zuora Coding AgentARCHIVED FILE

references/uat-test/execution/scripts/parse_debug_variables.py

4.41 KB · Oct 2, 2026 · 00:30 UTC

↓ Download file

#!/usr/bin/env python3
"""Parse captured test variables from API debug logs for UI execution."""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
_EXECUTION_DIR = SCRIPT_DIR.parent
sys.path.insert(0, str(_EXECUTION_DIR))
sys.path.insert(0, str(_EXECUTION_DIR / "tests"))

from tests.test_utils.uat_root import find_git_root, find_uat_root  # noqa: E402

_TR_KEY_RE = re.compile(r"^TR(\d+)$", re.I)
_RESPONSE_DATA_RE = re.compile(
    r"RESPONSE DATA:\s*\n(\{.*?\})\s*\n=+",
    re.DOTALL,
)
_DEBUG_VARIABLES_RE = re.compile(
    r"INFO:\s*DEBUG_VARIABLES:\s*(.+?)(?:\n|$)",
    re.MULTILINE,
)
_TEST_STATUS_RE = re.compile(r"^Status:\s*(PASSED|FAILED)\s*$", re.MULTILINE)


def _resolve_uat_root(args) -> Path:
    if getattr(args, "uat_root", None):
        return Path(args.uat_root).resolve()
    if getattr(args, "git_root", None):
        return find_uat_root(Path(args.git_root).resolve())
    return find_uat_root(find_git_root())


def normalize_tr_index(tr: str | int) -> str:
    text = str(tr).strip()
    if text.upper().startswith("TR"):
        return f"TR{int(text[2:])}"
    return f"TR{int(text)}"


def camel_to_snake(name: str) -> str:
    return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()


def merge_response_variables(target: dict, payload: dict) -> None:
    for key, value in payload.items():
        if value is None:
            continue
        if isinstance(value, (dict, list)):
            continue
        target[camel_to_snake(key)] = value
        target[key] = value


def parse_debug_variables_line(line: str) -> dict[str, str]:
    variables: dict[str, str] = {}
    for token in line.strip().split():
        if "=" not in token:
            continue
        key, value = token.split("=", 1)
        variables[key.strip()] = value.strip()
    return variables


def parse_debug_log_text(text: str) -> dict:
    variables: dict = {}
    for match in _RESPONSE_DATA_RE.finditer(text):
        try:
            payload = json.loads(match.group(1))
        except json.JSONDecodeError:
            continue
        if isinstance(payload, dict):
            merge_response_variables(variables, payload)

    debug_line = _DEBUG_VARIABLES_RE.search(text)
    if debug_line:
        variables.update(parse_debug_variables_line(debug_line.group(1)))

    status_match = _TEST_STATUS_RE.search(text)
    api_test_passed = status_match.group(1) == "PASSED" if status_match else None
    return {
        "variables": variables,
        "api_test_passed": api_test_passed,
    }


def find_latest_debug_log(uat_root: Path, feature_id: str, tr_index: str) -> Path | None:
    tr_key = normalize_tr_index(tr_index)
    debug_dir = uat_root / "execution" / "debugging"
    if not debug_dir.is_dir():
        return None
    matches = sorted(
        debug_dir.glob(f"{feature_id}_{tr_key}_*.log"),
        key=lambda p: p.stat().st_mtime,
    )
    return matches[-1] if matches else None


def parse_debug_log_file(path: Path) -> dict:
    parsed = parse_debug_log_text(path.read_text(encoding="utf-8"))
    parsed["debug_log"] = str(path)
    return parsed


def cmd_parse(args):
    uat = _resolve_uat_root(args)
    tr_key = normalize_tr_index(args.tr)
    debug_log = find_latest_debug_log(uat, args.feature, tr_key)
    if debug_log is None:
        print(
            json.dumps(
                {
                    "feature": args.feature,
                    "tr": tr_key,
                    "debug_log": None,
                    "variables": {},
                    "api_test_passed": False,
                    "error": "debug_log_not_found",
                }
            )
        )
        sys.exit(1)

    result = parse_debug_log_file(debug_log)
    result["feature"] = args.feature
    result["tr"] = tr_key
    print(json.dumps(result))
    if result.get("api_test_passed") is False:
        sys.exit(2)


def main():
    parser = argparse.ArgumentParser(description="Parse variables from UAT API debug logs")
    parser.add_argument("--feature", required=True, help="Testmatrix feature id")
    parser.add_argument("--tr", required=True, help="TR number (1, TR1, etc.)")
    parser.add_argument("--git-root", help="Git project root (resolves UAT root)")
    parser.add_argument("--uat-root", help="UAT workspace root override")
    parser.set_defaults(func=cmd_parse)
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()

SHA-256: 6330c6f9d06d17e08ee0e949bcbd1606155a898d3b45bbb0abb4b33a31bff79b