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.
124 lines
6.2 KiB
124 lines
6.2 KiB
# -*- coding: utf-8 -*-
|
|
"""票 02 · D-01 专项运行时断言(opportunity-fix 口径:新建商机 fail-fast + 首阶段落位)。
|
|
|
|
D-01(opportunity-fix):系统无发布中商机阶段模板时,新建商机 500 → 修复为:
|
|
a) fail-fast 60001「系统未配置发布中的商机阶段模板…」(OpportunityIntakeException → seam 译 BusinessErrorException)
|
|
b) OPP_STAGE_TPL_01 系统预设模板受 64018 保护不可停用
|
|
c) 正常路径:新建商机落真实首阶段(currentStageId 非空)+ progress 5 节点首节点 CURRENT
|
|
|
|
断言 1/2/3 走运行时 API;断言 4(60001 fail-fast)需「无发布中模板」场景——
|
|
系统预设模板 API 不可停(64018 保护),故 DB 临时置停用 → 验证 → finally 恢复 → 复验恢复。
|
|
"""
|
|
import io
|
|
import json
|
|
import sys
|
|
|
|
import pymysql
|
|
import requests
|
|
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
|
|
BASE = 'http://localhost:8080'
|
|
UID_A = '744842565802524672'
|
|
TPL_CODE = 'OPP_STAGE_TPL_01' # 系统预设模板(R01 已验 64018 停用保护)
|
|
|
|
r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': UID_A}, timeout=15)
|
|
assert r.json().get('code') == 0, r.text
|
|
H = {'Authorization': 'Bearer ' + r.json()['data']}
|
|
|
|
results = []
|
|
|
|
|
|
def rec(name, ok, ev=''):
|
|
results.append((name, ok, ev))
|
|
print(f" [{'✅' if ok else '❌'}] {name}" + (f" —— {ev}" if ev else ''))
|
|
|
|
|
|
print('== D-01 专项运行时断言 ==')
|
|
|
|
# ---------- 探查:阶段模板表 + 发布中模板 ----------
|
|
conn = pymysql.connect(host='8.129.84.155', port=3306, user='root', password='Itc@123456',
|
|
database='crm', charset='utf8mb4', autocommit=True)
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT id, template_code, template_name, status, is_default FROM "
|
|
"opportunity_stage_template WHERE deleted=0 AND status=2")
|
|
pub = cur.fetchall()
|
|
print(f'发布中阶段模板 {len(pub)} 个: {[(str(p[0]), p[1], p[4]) for p in pub]}')
|
|
tpl_row = next((p for p in pub if p[1] == TPL_CODE), None)
|
|
|
|
# ---------- 断言 1:新建商机落真实首阶段(用 core F01 新建的商机) ----------
|
|
d = requests.post(f'{BASE}/api/opportunity/page',
|
|
headers={**H, 'Content-Type': 'application/x-www-form-urlencoded'},
|
|
data={'viewType': 'MINE', 'current': 1, 'size': 20, 'keyword': 'e2e-F01'},
|
|
timeout=30).json()
|
|
rows = (d.get('data') or {}).get('content') or []
|
|
f01_id = str(rows[0]['id']) if rows else None
|
|
rec('前置:F01 新建商机可查', bool(f01_id), f'id={f01_id}')
|
|
if f01_id:
|
|
det = requests.get(f'{BASE}/api/opportunity/detail', headers=H, params={'id': f01_id}, timeout=30).json()
|
|
dd = det.get('data') or {}
|
|
stage_id = dd.get('currentStageId') or dd.get('stageId')
|
|
rec('D-01 c) 新建商机 currentStageId 落真实首节点(非空)', bool(stage_id),
|
|
f"currentStageId={stage_id}")
|
|
|
|
# ---------- 断言 2:progress 5 节点 + 首节点 CURRENT ----------
|
|
p = requests.get(f'{BASE}/api/opportunity/stage/progress', headers=H, params={'oppId': f01_id}, timeout=30).json()
|
|
nodes = (p.get('data') or {}).get('nodes') or []
|
|
cur_nodes = [n for n in nodes if n.get('nodeState') == 'CURRENT']
|
|
rec('D-01 c) progress 节点数 = 5(首节点 CURRENT)', len(nodes) == 5 and len(cur_nodes) == 1
|
|
and int(cur_nodes[0].get('seqNo') or 0) == 1,
|
|
f"nodes={len(nodes)} current seq={cur_nodes[0].get('seqNo') if cur_nodes else None}")
|
|
|
|
# ---------- 断言 3:64018 系统预设模板停用保护(运行时复验) ----------
|
|
if tpl_row:
|
|
tpl_id = str(tpl_row[0])
|
|
rr = requests.post(f'{BASE}/api/rule/opp-stage-template/disable', headers=H,
|
|
params={'id': tpl_id}, timeout=30).json()
|
|
rec('D-01 b) 系统预设模板 disable → 64018', rr.get('code') == 64018,
|
|
f"code={rr.get('code')} msg={str(rr.get('message'))[:50]}")
|
|
|
|
# ---------- 断言 4:60001 fail-fast(DB 临时停用全部发布中模板 → 新建 → 恢复) ----------
|
|
def try_create():
|
|
rr = requests.post(f'{BASE}/api/opportunity',
|
|
headers={**H, 'Content-Type': 'application/x-www-form-urlencoded'},
|
|
data={'opportunityName': 'e2e-D01-failfast探针', 'oppSource': 'opp_source_03',
|
|
'industryCode': 'gov', 'bidForm': 'bid_form_02', 'localityType': 'locality_type_02',
|
|
'provinceCode': '510000', 'cityCode': '510100',
|
|
'partyAClear': 0, 'partyA': 'E2E甲方-D01'},
|
|
timeout=30)
|
|
rr.encoding = 'utf-8'
|
|
return rr.json()
|
|
|
|
fail_code = None
|
|
try:
|
|
cur.execute("UPDATE opportunity_stage_template SET status=3 WHERE deleted=0 AND status=2")
|
|
conn.commit()
|
|
b = try_create()
|
|
fail_code = b.get('code')
|
|
rec('D-01 a) 无发布中模板新建 → 60001 fail-fast', fail_code == 60001,
|
|
f"code={fail_code} msg={str(b.get('message'))[:60]}")
|
|
finally:
|
|
# 恢复:被临时停用的模板全部回到发布中(含系统预设 is_default)
|
|
cur.execute("UPDATE opportunity_stage_template SET status=2 WHERE deleted=0 AND status=3 "
|
|
"AND is_default=1")
|
|
conn.commit()
|
|
cur.execute("SELECT COUNT(*) FROM opportunity_stage_template WHERE deleted=0 AND status=2 AND is_default=1")
|
|
restored = cur.fetchone()[0]
|
|
rec('环境恢复:系统预设模板回发布中', restored >= 1, f'restored={restored}')
|
|
|
|
# 恢复后复验:新建恢复正常
|
|
b2 = try_create()
|
|
rec('D-01 恢复后新建恢复正常(fail-fast 不留痕)', b2.get('code') == 0, f"code={b2.get('code')}")
|
|
if b2.get('code') == 0 and b2.get('data'):
|
|
# 清理探针商机(软删口径走 DB,避免污染 MINE 视图断言)
|
|
new_id = b2['data'] if isinstance(b2['data'], (int, str)) else b2['data'].get('id')
|
|
cur.execute("UPDATE opportunity SET deleted=1 WHERE id=%s", (new_id,))
|
|
conn.commit()
|
|
print(f' (探针商机 {new_id} 已软删清理)')
|
|
|
|
conn.close()
|
|
|
|
ok_n = sum(1 for _, ok, _ in results if ok)
|
|
print(f"\n===== D-01 专项:{ok_n}/{len(results)} 通过 =====")
|
|
with open(r'd:\code\crm-backend-matt\.scratch\opportunity-acceptance\d01-runtime-result.json', 'w', encoding='utf-8') as fp:
|
|
json.dump([{'name': n, 'ok': ok, 'ev': e} for n, ok, e in results], fp, ensure_ascii=False, indent=1)
|
|
|