mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
038f846dad
- docs/elevator-api-parity: 计划/报告模板/示例 - tools/elevator_api_parity: 端点目录、fixtures、对拍 client/compare、报告生成 - scripts/run_elevator_parity: JDK8 构建 + 单测/对拍(无服务时跳过对拍用例) Made-with: Cursor Former-commit-id: 3d54a40e1a7ae0b1724261d4f18910a6f415f853
95 lines
2.8 KiB
Python
95 lines
2.8 KiB
Python
"""
|
|
Compare two JSON response bodies. CloudwalkResult: compare top-level `code` when
|
|
both parse as objects; also deep-compare with optional key stripping.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
from typing import Any, List, Set
|
|
|
|
# JSONPath-like: tuple of keys in order for nesting
|
|
DEFAULT_STRIP_PATHS: Set[tuple] = {
|
|
# ("data", "ts"),
|
|
}
|
|
|
|
|
|
def _strip(obj: Any, rules: Set[tuple], prefix: tuple) -> Any:
|
|
if not isinstance(obj, (dict, list)):
|
|
return obj
|
|
if isinstance(obj, list):
|
|
return [_strip(x, rules, prefix + (i,)) for i, x in enumerate(obj)]
|
|
out = copy.deepcopy(obj)
|
|
for k, v in list(obj.items()) if isinstance(obj, dict) else []:
|
|
p = prefix + (k,)
|
|
if p in rules:
|
|
if k in out:
|
|
del out[k]
|
|
continue
|
|
if isinstance(v, (dict, list)):
|
|
out[k] = _strip(v, rules, p)
|
|
return out
|
|
|
|
|
|
def parse_json_loose(text: str) -> Any:
|
|
if not (text or "").strip():
|
|
return None
|
|
return json.loads(text)
|
|
|
|
|
|
def normalize(
|
|
data: Any,
|
|
strip_paths: Set[tuple] | None = None,
|
|
) -> str:
|
|
rules = strip_paths if strip_paths is not None else DEFAULT_STRIP_PATHS
|
|
s = _strip(data, rules, tuple())
|
|
return json.dumps(s, ensure_ascii=False, sort_keys=True, default=str)
|
|
|
|
|
|
def business_code(data: Any) -> str | None:
|
|
if isinstance(data, dict) and "code" in data:
|
|
c = data.get("code")
|
|
return str(c) if c is not None else None
|
|
return None
|
|
|
|
|
|
def same_shape(old_text: str, new_text: str) -> bool:
|
|
try:
|
|
a = parse_json_loose(old_text)
|
|
b = parse_json_loose(new_text)
|
|
except json.JSONDecodeError:
|
|
return False
|
|
if type(a) != type(b):
|
|
return False
|
|
if isinstance(a, dict) and isinstance(b, dict) and a.keys() == b.keys():
|
|
return all(type(a[k]) is type(b[k]) for k in a)
|
|
if isinstance(a, list) and isinstance(b, list):
|
|
if len(a) == len(b) and len(a) == 0:
|
|
return True
|
|
return False
|
|
|
|
|
|
def describe_diff(
|
|
old_text: str, new_text: str, compare_norm: str = "deep"
|
|
) -> str:
|
|
try:
|
|
o = parse_json_loose(old_text)
|
|
n = parse_json_loose(new_text)
|
|
except json.JSONDecodeError as e:
|
|
return f"json parse: {e}"
|
|
if compare_norm == "code_only":
|
|
c1, c2 = business_code(o), business_code(n)
|
|
if c1 == c2:
|
|
return ""
|
|
return f"business code: old={c1!r} new={c2!r}"
|
|
no = normalize(o)
|
|
nn = normalize(n)
|
|
if no == nn:
|
|
return ""
|
|
if business_code(o) is not None and business_code(n) is not None and business_code(o) != business_code(
|
|
n
|
|
):
|
|
return f"normalized JSON differs; first code: old={business_code(o)} new={business_code(n)}"
|
|
return "normalized JSON differs (see full bodies in test log)"
|