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.
202 lines
10 KiB
202 lines
10 KiB
# -*- coding: utf-8 -*-
|
|
"""acceptance-prep-testenv.py — 测试环境验收准备(诊断 + 构建代际判别 + 必要时造数)。
|
|
|
|
三段:
|
|
A 诊断:POST /api/customer/workspace/page(workspace=mine/pool)原始响应落盘
|
|
B 判别:GET /api/rule/customer/dedup(rework 代际分界,返工新增端点)
|
|
+ create 探针客户 → edit 不带 version(67005=含 defectfix / 67001+乐观锁=旧构建)
|
|
探针客户用完即 archive,零活跃残留
|
|
C 造数:仅当「新构建 + 库空」时造 6 个 e2c-acc-* 验收客户(3 mine / 2 pool / 1 archived)
|
|
旧构建则直接劝返:先发版(mvn clean package → 上传 jar → deploy/restart.sh)再验收
|
|
|
|
用法:py -X utf8 .scratch/customer-defectfix/acceptance-prep-testenv.py
|
|
产物:acceptance-prep-testenv-result.json(同目录)
|
|
"""
|
|
import io
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
import requests
|
|
|
|
BASE = 'https://ai.itc.vip/crm-api'
|
|
ADMIN = '739564171091247104' # 罗伟健(admin)
|
|
BUDDY = '744842318024015872' # 曾偲青(BUDDY)
|
|
OUT = os.path.dirname(os.path.abspath(__file__))
|
|
RESULT = os.path.join(OUT, 'acceptance-prep-testenv-result.json')
|
|
STAMP = time.strftime('%H%M%S')
|
|
|
|
ev = {'base': BASE, 'ts': time.strftime('%Y-%m-%d %H:%M:%S')}
|
|
VERDICT = []
|
|
|
|
|
|
def note(msg):
|
|
print(msg)
|
|
VERDICT.append(msg)
|
|
|
|
|
|
# ---- token ----
|
|
r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': ADMIN}, timeout=15)
|
|
d = (r.json() or {}).get('data') if r.status_code == 200 else None
|
|
tok = d if isinstance(d, str) else (d or {}).get('token')
|
|
if not tok:
|
|
note('debug token 不可用 —— 先解决测试环境 verify profile 再谈验收')
|
|
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
|
sys.exit(1)
|
|
S = requests.Session()
|
|
S.headers.update({'Authorization': f'Bearer {tok}'})
|
|
|
|
|
|
def body_of(r):
|
|
try:
|
|
return r.json() or {}
|
|
except Exception:
|
|
return {'_raw': r.text[:120]}
|
|
|
|
|
|
# ---- A. 诊断 workspace/page 原始响应(demo 同款:POST /api/customer/workspace/page + workspace=mine/pool)----
|
|
mine_raw = body_of(S.post(f'{BASE}/api/customer/workspace/page', data={'workspace': 'mine', 'current': 1, 'size': 3}, timeout=20))
|
|
pool_raw = body_of(S.post(f'{BASE}/api/customer/workspace/page', data={'workspace': 'pool', 'current': 1, 'size': 3}, timeout=20))
|
|
ev['mine_raw'] = {k: mine_raw.get(k) for k in ('code', 'message')}
|
|
ev['mine_raw']['data_keys'] = sorted((mine_raw.get('data') or {}).keys()) if isinstance(mine_raw.get('data'), dict) else str(type(mine_raw.get('data')))
|
|
ev['pool_raw'] = {k: pool_raw.get(k) for k in ('code', 'message')}
|
|
md = mine_raw.get('data') or {}
|
|
pd = pool_raw.get('data') or {}
|
|
mine_rows = md.get('content') or md.get('records') or []
|
|
pool_rows = pd.get('content') or pd.get('records') or []
|
|
mine_total = md.get('total')
|
|
pool_total = pd.get('total')
|
|
note(f"A. mine/page code={mine_raw.get('code')} msg={str(mine_raw.get('message'))[:60]} total={mine_total} data_keys={ev['mine_raw']['data_keys']}")
|
|
note(f" pool/page code={pool_raw.get('code')} msg={str(pool_raw.get('message'))[:60]} total={pool_total}")
|
|
|
|
# ---- B. 构建代际判别 ----
|
|
rework_ok = False
|
|
fix_ok = False
|
|
probe_id = None
|
|
|
|
# B1. /api/rule/customer/dedup(返工新增端点,e2e-incr DEDUP_G 同款路径)
|
|
rd = S.get(f'{BASE}/api/rule/customer/dedup', timeout=20)
|
|
bd = body_of(rd)
|
|
ev['dedup_probe'] = {'http': rd.status_code, 'code': bd.get('code'), 'data': str(bd.get('data'))[:120]}
|
|
if rd.status_code == 200 and bd.get('code') in (0, '0', 200, '200'):
|
|
rework_ok = True
|
|
note(f"B1. rule/customer/dedup → 200 code=0 —— rework 后契约在(data={str(bd.get('data'))[:80]})")
|
|
else:
|
|
note(f"B1. rule/customer/dedup → HTTP {rd.status_code} code={bd.get('code')} —— **pre-rework 旧构建**(该端点不存在)")
|
|
|
|
# B2. 探针客户:库里有现成就用现成的,否则 create 一个
|
|
fid = None
|
|
if isinstance(mine_total, (int, str)) and str(mine_total) not in ('0', 'None') and mine_rows:
|
|
fid = mine_rows[0]['id']
|
|
note(f"B2. 用现有客户做判别样本:{mine_rows[0].get('customerNo')} {mine_rows[0].get('customerName')}")
|
|
else:
|
|
dicts = {}
|
|
for g in ('customer_type', 'industry'):
|
|
items = (body_of(S.get(f'{BASE}/api/dict/item/enabled-list', params={'groupCode': g}, timeout=15)).get('data') or [])
|
|
dicts[g] = items
|
|
ctype = (dicts['customer_type'][0]['code'] if dicts['customer_type'] else 'TYPE_A')
|
|
ind = (dicts['industry'][0]['code'] if dicts['industry'] else 'I_A')
|
|
rc = S.post(f'{BASE}/api/customer/create', data={
|
|
'customerName': f'e2c-accprobe-{STAMP}', 'customerType': ctype,
|
|
'provinceCode': '440000', 'cityCode': '440100', 'districtCode': '440103',
|
|
'industryCode': ind, 'customerStarLevel': 3, 'relationStarLevel': 3,
|
|
'isBizNegotiated': 0, 'isChild': 0, 'ownerUserId': ADMIN,
|
|
}, timeout=30)
|
|
br = body_of(rc)
|
|
ev['create_probe'] = {'http': rc.status_code, 'code': br.get('code'), 'message': str(br.get('message'))[:100]}
|
|
ddata = br.get('data')
|
|
fid = (ddata or {}).get('id') if isinstance(ddata, dict) else ddata
|
|
if fid:
|
|
note(f"B2. create 探针客户 OK id={fid}(扁平契约可用)")
|
|
else:
|
|
note(f"B2. create 失败 code={br.get('code')} msg={str(br.get('message'))[:80]} —— 契约形态或构建代际需人工看")
|
|
|
|
# B3. edit 不带 version(defectfix 分界;CAS 前置失败无写库)
|
|
if fid:
|
|
det = body_of(S.get(f'{BASE}/api/customer/detail', params={'id': fid}, timeout=20))
|
|
dd = det.get('data') or {}
|
|
form = {
|
|
'customerName': dd.get('customerName') or f'e2c-accprobe-{STAMP}',
|
|
'customerType': dd.get('customerType'), 'provinceCode': dd.get('provinceCode'),
|
|
'cityCode': dd.get('cityCode'), 'districtCode': dd.get('districtCode'),
|
|
'industryCode': dd.get('industryCode'),
|
|
'customerStarLevel': dd.get('customerStarLevel') or 3,
|
|
'relationStarLevel': dd.get('relationStarLevel') or 3,
|
|
'isBizNegotiated': dd.get('isBizNegotiated') if dd.get('isBizNegotiated') is not None else 0,
|
|
'isChild': dd.get('isChild') if dd.get('isChild') is not None else 0,
|
|
}
|
|
re_ = S.post(f'{BASE}/api/customer/edit', params={'id': fid}, data=form, timeout=60)
|
|
be = body_of(re_)
|
|
ev['d03_probe'] = {'http': re_.status_code, 'code': be.get('code'), 'message': str(be.get('message'))[:80]}
|
|
if re_.status_code in (404, 405):
|
|
note(f"B3. edit → HTTP {re_.status_code} —— **pre-rework 旧构建**(扁平 edit 不存在)")
|
|
elif str(be.get('code')) == '67005':
|
|
fix_ok = True
|
|
note("B3. edit 缺 version → 67005 —— **defectfix 修复已部署**")
|
|
elif str(be.get('code')) == '67001' and '乐观锁' in str(be.get('message')):
|
|
note("B3. edit 缺 version → 67001(乐观锁)—— **defectfix 修复未部署(旧构建)**")
|
|
else:
|
|
note(f"B3. edit 返回异常形态 code={be.get('code')} msg={str(be.get('message'))[:70]} —— 需人工看")
|
|
|
|
# B4. 探针客户归档清场
|
|
if fid and ev.get('create_probe'):
|
|
ra = S.post(f'{BASE}/api/customer/archive', params={'id': fid}, timeout=20)
|
|
ev['archive_probe'] = {'http': ra.status_code, 'code': body_of(ra).get('code')}
|
|
note(f"B4. 探针客户已 archive 清场(code={ev['archive_probe']['code']})")
|
|
|
|
# ---- 结论分支 ----
|
|
if not (rework_ok and fix_ok):
|
|
note("")
|
|
note("== 结论:测试环境 jar 落后于客户域交付(rework 2026-09-05 / defectfix 2026-09-06)==")
|
|
note(" 验收前必须先发版:mvn clean package -DskipTests → 上传 crm-app-1.0.0-SNAPSHOT.jar 到服务器项目根 → ./deploy/restart.sh")
|
|
note(" 发版后重跑本脚本确认 B1/B3 双绿,再进入验收")
|
|
ev['verdict'] = 'OLD_BUILD'
|
|
ev['notes'] = VERDICT
|
|
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
|
sys.exit(0)
|
|
|
|
# ---- C. 造验收数据(新构建 + 库空才造)----
|
|
have = str(mine_total) not in ('None', '0') or str(pool_total) not in ('None', '0')
|
|
if have:
|
|
note(f"C. 库里有数据(mine={mine_total} pool={pool_total})—— 不重复造数,直接验收")
|
|
ev['verdict'] = 'READY_HAS_DATA'
|
|
else:
|
|
note("C. 库为空,造一组验收数据:e2c-acc-* ×6(3 mine / 2 pool / 1 archived)")
|
|
items = body_of(S.get(f'{BASE}/api/dict/item/enabled-list', params={'groupCode': 'customer_type'}, timeout=15)).get('data') or []
|
|
inds = body_of(S.get(f'{BASE}/api/dict/item/enabled-list', params={'groupCode': 'industry'}, timeout=15)).get('data') or []
|
|
ctype = items[0]['code'] if items else 'TYPE_A'
|
|
ind = inds[0]['code'] if inds else 'I_A'
|
|
names = ['恒信达科技', '蓝湾生物', '中晟建材', '启明教育', '云帆物流']
|
|
made = []
|
|
for i, nm in enumerate(names):
|
|
star = (i % 5) + 1
|
|
rc = S.post(f'{BASE}/api/customer/create', data={
|
|
'customerName': f'e2c-acc-{STAMP}-{nm}', 'customerType': ctype,
|
|
'provinceCode': '440000', 'cityCode': '440100', 'districtCode': '440103',
|
|
'industryCode': ind, 'customerStarLevel': star, 'relationStarLevel': min(star, 5),
|
|
'isBizNegotiated': i % 2, 'isChild': 0, 'ownerUserId': ADMIN,
|
|
}, timeout=30)
|
|
br = body_of(rc)
|
|
ddata = br.get('data')
|
|
cid = (ddata or {}).get('id') if isinstance(ddata, dict) else ddata
|
|
if not cid:
|
|
note(f" x create {nm} 失败 code={br.get('code')} msg={str(br.get('message'))[:60]}")
|
|
continue
|
|
made.append(str(cid))
|
|
if i >= 3: # 后两家抛公海
|
|
rp = S.post(f'{BASE}/api/customer/release-pool', params={'id': cid}, timeout=20)
|
|
note(f" + e2c-acc-{STAMP}-{nm} id={cid} star={star} → pool(code={body_of(rp).get('code')})")
|
|
else:
|
|
note(f" + e2c-acc-{STAMP}-{nm} id={cid} star={star} → mine")
|
|
if made:
|
|
ra = S.post(f'{BASE}/api/customer/archive-batch', params={'ids': ','.join(made[:1])}, timeout=20)
|
|
note(f" + 首家 archive → 已归档视图(code={body_of(ra).get('code')})")
|
|
note(f"C. 造数完成:mine 2 / pool 2 / archived 1(含探针归档)—— demo 里可直接走查")
|
|
ev['verdict'] = 'READY_SEEDED'
|
|
ev['seeded_ids'] = made
|
|
|
|
ev['notes'] = VERDICT
|
|
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
|
print(f"\n证据已落盘 {RESULT}")
|
|
|