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

# -*- 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)