import json, subprocess, tempfile, os from urllib.parse import quote def q(text): return quote(text, encoding='utf-8') def api(method, url, body=None): cmd = ['sh', '.tmp_api.sh', method, url] tmp = None if body is not None: fd, tmp = tempfile.mkstemp(suffix='.json') with os.fdopen(fd, 'w', encoding='utf-8') as f: json.dump(body, f, ensure_ascii=False) cmd.append(tmp) out = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8').stdout.strip() if tmp: os.remove(tmp) try: return json.loads(out) except Exception: return {'raw': out[:300]} def res(tag, r): print(f'{tag}: code={r.get("code", "?")} msg={r.get("msg", str(r)[:200])}') # 测试数据刻意避开演示号段(会员 126~145、服务商 33141000~),便于按精确 id 清理 base = { 'companyName': '联调测试服务有限公司', 'contactName': '测试员', 'contactPhone': '13800000900', 'creditCode': '91310101TEST0001XX', 'legalPerson': '测试法人', 'region': '上海', 'registerDate': '2019-05-20', 'address': '上海市黄浦区测试路 1 号', 'l1Quota': 1, 'l2Quota': 2, 'l3Quota': 3, 'userName': 'provider900', 'password': 'test12345', 'confirmPassword': 'test12345', } print('=== A. 新增的负例(必须全被拦下) ===') res('A1 登录账号重复(provider126)', api('POST', '/account/user/provider', dict(base, userName='provider126'))) res('A2 手机号为空', api('POST', '/account/user/provider', dict(base, contactPhone=''))) res('A3 密码为空', api('POST', '/account/user/provider', dict(base, password='', confirmPassword=''))) res('A4 两次密码不一致', api('POST', '/account/user/provider', dict(base, confirmPassword='test12346'))) res('A5 密码过短', api('POST', '/account/user/provider', dict(base, password='1234', confirmPassword='1234'))) res('A6 公司名称为空', api('POST', '/account/user/provider', dict(base, companyName=''))) res('A7 联系人为空', api('POST', '/account/user/provider', dict(base, contactName=''))) res('A8 登录账号为空', api('POST', '/account/user/provider', dict(base, userName=''))) res('A9 手机号已被占用(演示的 13800000127)', api('POST', '/account/user/provider', dict(base, contactPhone='13800000127'))) print() print('=== B. 新增正例 ===') r = api('POST', '/account/user/provider', base) res('B1 新增', r) r = api('GET', '/account/user/provider/list?' + 'companyName=' + q('联调测试')) new = (r.get('rows') or [None])[0] print('B2 列表能查到:', bool(new)) new_id = new['id'] if new else None new_uid = new['userId'] if new else None print('B3 新服务商 id =', new_id, '| 绑定账号 userId =', new_uid) if new: print('B4 回读:', new['userName'], '|', new['companyName'], '|', new['region'], '| 权益', new['l1Quota'], new['l2Quota'], new['l3Quota'], '| 已授权数', new['l1Granted'], new['l2Granted'], new['l3Granted'], '| status', new['status']) print() print('=== C. 修改 ===') res('C1 无 id', api('PUT', '/account/user/provider', dict(base, companyName='无id改名'))) res('C2 不存在的 id', api('PUT', '/account/user/provider', dict(base, id=999999))) res('C3 改成别人的登录账号(provider127)', api('PUT', '/account/user/provider', dict(base, id=new_id, userName='provider127'))) res('C4 改成别人的手机号(13800000128)', api('PUT', '/account/user/provider', dict(base, id=new_id, contactPhone='13800000128'))) # 密码留空即不改:这一条的验证在 DB 侧逐字比对 password 列(见脚本尾部输出的 SQL) res('C5 正常修改(改公司名/联系人/账号/手机号/地区,密码留空)', api('PUT', '/account/user/provider', dict(base, id=new_id, companyName='联调测试服务集团有限公司', contactName='测试员改', userName='provider901', contactPhone='13800000901', region='北京', l1Quota=7, l2Quota=8, l3Quota=9, password='', confirmPassword=''))) d = api('GET', '/account/user/provider/%s' % new_id)['data'] print('C6 修改后:', d['companyName'], '|', d['contactName'], '| 账号', d['userName'], '| 手机号', d['contactPhone'], '| 地区', d['region'], '| 权益', d['l1Quota'], d['l2Quota'], d['l3Quota']) print('C7 详情不含 password 字段 =', 'password' not in d) print() print('=== D. 状态(只改服务商资格,不动会员账号) ===') res('D1 非法状态', api('PUT', '/account/user/provider/changeStatus', {'id': new_id, 'status': '9'})) res('D2 禁用', api('PUT', '/account/user/provider/changeStatus', {'id': new_id, 'status': '1'})) d = api('GET', '/account/user/provider/%s' % new_id)['data'] print('D3 禁用后 provider.status =', d['status'], '| 权益未被清零:', d['l1Quota'], d['l2Quota'], d['l3Quota']) res('D4 启用', api('PUT', '/account/user/provider/changeStatus', {'id': new_id, 'status': '0'})) print() print('=== E. 对抗性验证:服务商标识不能从会员接口置位 ===') # 会员新增接口的请求体里塞 providerFlag=1,落库必须仍是 0 r = api('POST', '/account/user', {'userName': 'probe_flag_user', 'phone': '13800000999', 'password': 'probe12345', 'nickname': '标识探针', 'providerFlag': '1'}) res('E1 建一条带 providerFlag=1 的会员', r) r = api('GET', '/account/user/list?phone=13800000999') probe = (r.get('rows') or [None])[0] probe_uid = probe['userId'] if probe else None print('E2 探针会员 userId =', probe_uid, '(DB 侧核对 provider_flag 必须为 0,见尾部 SQL)') print() print('===== 待清理:服务商 id =', new_id, '| 服务商账号 userId =', new_uid, '| 探针会员 userId =', probe_uid)