You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
6.7 KiB
119 lines
6.7 KiB
|
14 hours ago
|
# -*- coding: utf-8 -*-
|
||
|
|
"""票 17 复测 · 完整主链回归(API 全链路)
|
||
|
|
建项 → 报备通过(0→1双写) → 逐级推进 → 3→5 连跳 → 多步回退 → 推回 5 → 标记赢单锁死(终态三锁)。
|
||
|
|
产物: retest-mainchain-result.json
|
||
|
|
"""
|
||
|
|
import io, json, pathlib, sys, time, urllib.request, urllib.parse, urllib.error
|
||
|
|
|
||
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||
|
|
BASE = "http://localhost:8080"
|
||
|
|
AUD = pathlib.Path(r"E:\code\crm-backend-matt\.scratch\crm-project\audit")
|
||
|
|
DIRECTOR = "99021002"
|
||
|
|
CUSTOMER, CARD = "99021001", "99021008"
|
||
|
|
|
||
|
|
R = []
|
||
|
|
def step(name, ok, note=""):
|
||
|
|
R.append({"step": name, "ok": bool(ok), "note": str(note)[:300]})
|
||
|
|
print(("PASS " if ok else "FAIL ") + name + ((" | " + str(note)) if note else ""))
|
||
|
|
|
||
|
|
def _req(method, path, body=None, tok=None):
|
||
|
|
data = urllib.parse.urlencode(body).encode() if body is not None else None
|
||
|
|
headers = {"Authorization": "Bearer " + tok} if tok else {}
|
||
|
|
if data: headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
|
||
|
|
r = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
|
||
|
|
try:
|
||
|
|
with urllib.request.urlopen(r, timeout=20) as resp:
|
||
|
|
return json.loads(resp.read().decode("utf-8", "replace"))
|
||
|
|
except urllib.error.HTTPError as e:
|
||
|
|
try: return json.loads(e.read().decode("utf-8", "replace"))
|
||
|
|
except Exception: return {"__status": e.code}
|
||
|
|
except Exception as e:
|
||
|
|
return {"__error": str(e)}
|
||
|
|
|
||
|
|
def detail(pid):
|
||
|
|
return _req("GET", "/api/project/detail?id=" + pid, tok=TOK)["data"]
|
||
|
|
|
||
|
|
TOK = _req("GET", "/api/auth/debug/token?userId=" + DIRECTOR)["data"]
|
||
|
|
TOK = TOK if isinstance(TOK, str) else TOK["token"]
|
||
|
|
|
||
|
|
# 1. 建项 → stage0 / filing=1 / status=1
|
||
|
|
nm = f"R17-主链-{time.strftime('%H%M%S')}"
|
||
|
|
j = _req("POST", "/api/project/create", {"projectName": nm, "customerId": CUSTOMER, "schemeCardId": CARD}, tok=TOK)
|
||
|
|
pid = str(j["data"])
|
||
|
|
d = detail(pid)
|
||
|
|
step("1 建项:stage=0 报备审核中", j.get("code") == 0 and str(d["projectStage"]) == "0"
|
||
|
|
and str(d["filingStatus"]) == "1" and str(d["projectStatus"]) == "1",
|
||
|
|
f"stage={d['projectStage']} filing={d['filingStatus']} status={d['projectStatus']}")
|
||
|
|
|
||
|
|
# 2. 冲突解除=报备审核通过(双写 0→1)
|
||
|
|
j = _req("POST", "/api/project/filing/review", {"id": pid, "approved": "true", "remark": "主链复测"}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("2 冲突解除双写:filing=2 + stage 0→1", j.get("code") == 0 and str(d["projectStage"]) == "1"
|
||
|
|
and str(d["filingStatus"]) == "2", f"stage={d['projectStage']} filing={d['filingStatus']}")
|
||
|
|
|
||
|
|
# 3. 逐级推进 1→2→3
|
||
|
|
_req("POST", "/api/project/stage/advance", {"id": pid}, tok=TOK)
|
||
|
|
j = _req("POST", "/api/project/stage/advance", {"id": pid}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("3 逐级推进 1→2→3", j.get("code") == 0 and str(d["projectStage"]) == "3", f"stage={d['projectStage']}")
|
||
|
|
|
||
|
|
# 4. 3→5 唯一连跳(无需招投标)
|
||
|
|
j = _req("POST", "/api/project/stage/advance", {"id": pid, "skipBidding": "true"}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("4 3→5 唯一连跳", j.get("code") == 0 and str(d["projectStage"]) == "5", f"stage={d['projectStage']}")
|
||
|
|
|
||
|
|
# 5. 多步回退 5→2
|
||
|
|
j = _req("POST", "/api/project/stage/rollback", {"id": pid, "targetStage": "2"}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("5 多步回退 5→2(非相邻)", j.get("code") == 0 and str(d["projectStage"]) == "2", f"stage={d['projectStage']}")
|
||
|
|
|
||
|
|
# 6. 推回 5:2→3 → 3→5
|
||
|
|
_req("POST", "/api/project/stage/advance", {"id": pid}, tok=TOK)
|
||
|
|
j = _req("POST", "/api/project/stage/advance", {"id": pid, "skipBidding": "true"}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("6 推回 5(2→3→连跳5)", j.get("code") == 0 and str(d["projectStage"]) == "5", f"stage={d['projectStage']}")
|
||
|
|
|
||
|
|
# 7. 标记赢单 → stage5 闭环锁死
|
||
|
|
j = _req("POST", "/api/project/bid-result/mark", {"id": pid, "bidResult": "1",
|
||
|
|
"bidOpenTime": "2026-09-21 10:00:00", "bidOpenPlace": "主链复测开标地",
|
||
|
|
"bidAwardMethod": "竞争性谈判", "bidAwardAmount": "660000"}, tok=TOK)
|
||
|
|
d = detail(pid)
|
||
|
|
step("7 标记赢单:停 stage5 / status=1 / bid=1(闭环不进结项)",
|
||
|
|
j.get("code") == 0 and str(d["projectStage"]) == "5" and str(d["projectStatus"]) == "1"
|
||
|
|
and str(d["bidResult"]) == "1", f"stage={d['projectStage']} status={d['projectStatus']} bid={d['bidResult']}")
|
||
|
|
|
||
|
|
# 8. 赢单终态锁(先测四项锁,最后才碰 close):回退/推进/重复标记/审核 全拒
|
||
|
|
j1 = _req("POST", "/api/project/stage/rollback", {"id": pid, "targetStage": "2"}, tok=TOK)
|
||
|
|
j2 = _req("POST", "/api/project/stage/advance", {"id": pid}, tok=TOK)
|
||
|
|
j4 = _req("POST", "/api/project/bid-result/mark", {"id": pid, "bidResult": "2"}, tok=TOK)
|
||
|
|
j5 = _req("POST", "/api/project/filing/review", {"id": pid, "approved": "true"}, tok=TOK)
|
||
|
|
step("8 赢单终态锁:回退 68003 + 重复标记 68009 + 推进/审核 均拒",
|
||
|
|
j1.get("code") == 68003 and j4.get("code") == 68009
|
||
|
|
and j2.get("code") not in (0, None) and j5.get("code") not in (0, None),
|
||
|
|
f"rollback={j1.get('code')} advance={j2.get('code')} remark={j4.get('code')} filing={j5.get('code')}")
|
||
|
|
|
||
|
|
# 8b. 观察项(非回归):赢单态 close 仍 code=0 —— ensureCloseable 只拦已关闭/已结项,
|
||
|
|
# 票 05-08 原有行为(git 溯源 ensureCloseable 未被 16R 触碰),台账 4-3-11「全拒」对赢单态过宽,记档供产品复核。
|
||
|
|
j3 = _req("POST", "/api/project/close", {"id": pid, "closeType": "MANUAL", "closeReason": "复测8b观察项"}, tok=TOK)
|
||
|
|
step("8b [观察项·非回归] 赢单态关闭仍 code=0(票05-08 原有行为,与台账 4-3-11 记载有出入,记档)",
|
||
|
|
j3.get("code") == 0, f"close={j3.get('code')} {str(j3.get('message'))[:80]}")
|
||
|
|
d = detail(pid)
|
||
|
|
step("8c 关闭后进入已关闭终态锁:再推进/再标记全拒 68003",
|
||
|
|
str(d["projectStatus"]) == "2"
|
||
|
|
and _req("POST", "/api/project/stage/advance", {"id": pid}, tok=TOK).get("code") == 68003
|
||
|
|
and _req("POST", "/api/project/bid-result/mark", {"id": pid, "bidResult": "1"}, tok=TOK).get("code") == 68003,
|
||
|
|
f"status={d['projectStatus']}")
|
||
|
|
|
||
|
|
# 9. 动态事件流完整
|
||
|
|
tl = _req("GET", f"/api/project/timeline/list?projectId={pid}¤t=1&size=50", tok=TOK)["data"]
|
||
|
|
acts = [t.get("action") for t in tl.get("content") or []]
|
||
|
|
need = ["CREATE", "FILING_SUBMIT", "CONFLICT_RELEASE", "STAGE_ADVANCE", "STAGE_ROLLBACK", "BID_WIN"]
|
||
|
|
miss = [a for a in need if a not in acts]
|
||
|
|
step("9 动态留痕全集(建项/报备/冲突解除/推进/回退/赢单)", not miss,
|
||
|
|
f"missing={miss} actions={sorted(set(acts))}")
|
||
|
|
|
||
|
|
passed = sum(1 for r in R if r["ok"])
|
||
|
|
print(f"\n==== 主链回归 {passed}/{len(R)} PASS ====")
|
||
|
|
(AUD / "retest-mainchain-result.json").write_text(json.dumps(R, ensure_ascii=False, indent=1), encoding="utf-8")
|
||
|
|
sys.exit(0 if passed == len(R) else 1)
|