| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- # -*- coding: utf-8 -*-
- """客户端 api 第 3 批接口冒烟:用户中心 / 我的优惠券 / 余额积分明细 / 售后 / 充值 / 上传。
- 用法:起 Java 18080 后执行
- python tools/test_api_batch3.py
- 环境变量:YOSHOP_API_BASE(默认 http://127.0.0.1:18080)
- 说明:
- - 全部是需登录接口,脚本会往 Redis 写测试 token(yoshop:api:token:apitest-batch3)与短信验证码;
- - 会临时造订单、售后单、充值订单、支付方式、优惠券领取记录、上传文件、临时用户,
- finally 中全部清理并把用户资料(手机号 / 昵称 / 头像)恢复原值。
- """
- import json
- import os
- import re
- import socket
- import sys
- import pymysql
- import requests
- from _root import ROOT
- from redis_probe import cmd
- BASE = os.environ.get("YOSHOP_API_BASE", "http://127.0.0.1:18080")
- TOKEN = "apitest-batch3"
- USER_ID = 10001
- GOODS_ID = 10009
- SKU_ID = "10009"
- # 领取测试用券(满 100 减 10、领取后 7 天有效)
- COUPON_ID = 10001
- PLAN_ID = 10001
- EXPRESS_ID = 10007
- YML = str(ROOT / "yoshop-java/src/main/resources/application.yml")
- TEXT = open(YML, encoding="utf-8").read()
- DB_PWD = re.findall(r"password:\s*(.+)", TEXT)[0].strip().strip("\"'")
- REDIS_PWD = re.findall(r"password:\s*(.+)", TEXT)[1].strip().strip("\"'")
- S = requests.Session()
- S.trust_env = False
- passed = 0
- failed = 0
- created_orders = []
- created_refunds = []
- created_recharge_orders = []
- created_files = []
- temp_user_id = None
- user_backup = {}
- coupon_receive_backup = 0
- # 1x1 PNG
- PNG = bytes.fromhex(
- "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
- "0000000d4944415408d76368f8ffff3f0005fe02fea735e1f90000000049454e44ae426082"
- )
- def check(name, cond, detail=""):
- global passed, failed
- if cond:
- passed += 1
- print(f" PASS {name}")
- else:
- failed += 1
- print(f" FAIL {name} {detail}")
- def call(method, path, params=None, body=None, token=TOKEN, files=None, data=None):
- headers = {"platform": "MP-WEIXIN"}
- if token:
- headers["Access-Token"] = token
- if files is not None:
- resp = S.post(BASE + path, params=params or {}, files=files, data=data or {},
- headers=headers, timeout=60)
- elif method == "GET":
- resp = S.get(BASE + path, params=params or {}, headers=headers, timeout=60)
- else:
- resp = S.post(BASE + path, params=params or {}, json=body, headers=headers, timeout=60)
- try:
- return resp.json()
- except Exception:
- return {"status": resp.status_code, "raw": resp.text[:300]}
- def get(path, params=None, token=TOKEN):
- return call("GET", path, params=params, token=token)
- def post(path, body=None, params=None, token=TOKEN):
- return call("POST", path, params=params, body=body, token=token)
- def upload(path, token=TOKEN, check_login=1, name="test.png"):
- files = {"file": (name, PNG, "image/png")}
- return call("POST", path, token=token, files=files, data={"checkLogin": str(check_login)})
- def snake_keys(obj, prefix="$"):
- found = []
- if isinstance(obj, dict):
- for k, v in obj.items():
- if isinstance(k, str) and re.fullmatch(r"[a-z0-9]+(_[a-z0-9]+)+", k):
- found.append(f"{prefix}.{k}")
- found.extend(snake_keys(v, f"{prefix}.{k}"))
- elif isinstance(obj, list):
- for i, v in enumerate(obj[:3]):
- found.extend(snake_keys(v, f"{prefix}[{i}]"))
- return found
- def db(sql):
- conn = pymysql.connect(host="192.168.1.177", port=3306, user="root", password=DB_PWD,
- database="shop", charset="utf8mb4", autocommit=True)
- try:
- cur = conn.cursor()
- cur.execute(sql)
- return cur.fetchall()
- finally:
- conn.close()
- def db_exec(sql):
- conn = pymysql.connect(host="192.168.1.177", port=3306, user="root", password=DB_PWD,
- database="shop", charset="utf8mb4", autocommit=True)
- try:
- cur = conn.cursor()
- cur.execute(sql)
- return cur.lastrowid
- finally:
- conn.close()
- def redis_set(key, value):
- sock = socket.create_connection(("192.168.1.177", 6379), timeout=5)
- try:
- cmd(sock, "AUTH", REDIS_PWD)
- cmd(sock, "SET", key, str(value))
- cmd(sock, "EXPIRE", key, "3600")
- finally:
- sock.close()
- def field(obj, name, default=None):
- return obj.get(name, default) if isinstance(obj, dict) else default
- def make_order():
- """造一条已完成订单 + 订单商品,返回 (order_id, order_goods_id)"""
- order_no = "TESTB3" + str(os.getpid()) + str(len(created_orders))
- order_id = db_exec(
- "INSERT INTO yoshop_order (order_no, order_type, total_price, order_price, coupon_id, coupon_money, "
- "points_money, points_num, pay_price, update_price, buyer_remark, pay_method, pay_status, trade_id, "
- "delivery_type, express_price, delivery_status, receipt_status, order_status, points_bonus, "
- "is_settled, is_comment, order_source, order_source_id, order_source_data, platform, user_id, "
- "is_delete, store_id, create_time, update_time) VALUES "
- f"('{order_no}', 10, 80.00, 80.00, 0, 0.00, 0.00, 0, 80.00, 0.00, '', '', 20, 0, "
- f"10, 0.00, 20, 20, 30, 0, 1, 1, 10, 0, '[]', 'H5', {USER_ID}, 0, 10001, "
- "UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
- created_orders.append(order_id)
- goods_id = db_exec(
- "INSERT INTO yoshop_order_goods (goods_id, goods_type, goods_name, image_id, deduct_stock_type, "
- "spec_type, goods_sku_id, goods_props, content, goods_no, goods_price, line_price, goods_weight, "
- "is_user_grade, grade_ratio, grade_goods_price, grade_total_money, coupon_money, points_money, "
- "points_num, points_bonus, total_num, total_price, total_pay_price, delivery_status, delivery_num, "
- "is_comment, order_id, user_id, goods_source_id, store_id, create_time) VALUES "
- f"({GOODS_ID}, 10, '正宗荔浦芋头', 0, 10, 20, '{SKU_ID}', '[]', '', '', 80.00, 100.00, 4.5, "
- f"0, 0, 0.00, 0.00, 0.00, 0.00, 0, 0, 1, 80.00, 80.00, 20, 1, 1, {order_id}, {USER_ID}, 0, "
- "10001, UNIX_TIMESTAMP())")
- return order_id, goods_id
- def make_payment(client, method, is_default=1):
- return db_exec(
- "INSERT INTO yoshop_payment (client, method, is_must_template, template_id, is_enable, is_default, "
- f"others, store_id, create_time, update_time) VALUES ('{client}', '{method}', 0, 0, 1, {is_default}, "
- "'[]', 10001, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
- def cleanup():
- try:
- for order_id in created_orders:
- db_exec(f"DELETE FROM yoshop_order_goods WHERE order_id={order_id}")
- db_exec(f"DELETE FROM yoshop_order WHERE order_id={order_id}")
- for refund_id in created_refunds:
- db_exec(f"DELETE FROM yoshop_order_refund_image WHERE order_refund_id={refund_id}")
- db_exec(f"DELETE FROM yoshop_order_refund_address WHERE order_refund_id={refund_id}")
- db_exec(f"DELETE FROM yoshop_order_refund WHERE order_refund_id={refund_id}")
- for order_id in created_recharge_orders:
- db_exec(f"DELETE FROM yoshop_recharge_order_plan WHERE order_id={order_id}")
- db_exec(f"DELETE FROM yoshop_recharge_order WHERE order_id={order_id}")
- db_exec("DELETE FROM yoshop_payment WHERE store_id=10001 AND method IN ('wechat','balance') "
- "AND client='MP-WEIXIN'")
- db_exec(f"DELETE FROM yoshop_user_coupon WHERE user_id={USER_ID} AND coupon_id={COUPON_ID}")
- db_exec(f"UPDATE yoshop_coupon SET receive_num={coupon_receive_backup} WHERE coupon_id={COUPON_ID}")
- if temp_user_id:
- db_exec(f"DELETE FROM yoshop_user WHERE user_id={temp_user_id}")
- if user_backup:
- db_exec("UPDATE yoshop_user SET mobile='{}', nick_name='{}', avatar_id={} WHERE user_id={}".format(
- user_backup["mobile"], user_backup["nick_name"], user_backup["avatar_id"], USER_ID))
- for file_id in created_files:
- db_exec(f"DELETE FROM yoshop_upload_file WHERE file_id={file_id}")
- print(" CLEAN 测试数据已清理")
- except Exception as e: # noqa: BLE001
- print(" CLEAN 失败:", e)
- def main():
- global temp_user_id, coupon_receive_backup
- print("== 客户端 api 第 3 批(用户中心 / 优惠券 / 明细 / 售后 / 充值 / 上传) ==")
- redis_set("yoshop:api:token:" + TOKEN, USER_ID)
- row = db(f"SELECT mobile, nick_name, avatar_id FROM yoshop_user WHERE user_id={USER_ID}")[0]
- user_backup.update(mobile=row[0], nick_name=row[1], avatar_id=row[2])
- coupon_receive_backup = db(f"SELECT receive_num FROM yoshop_coupon WHERE coupon_id={COUPON_ID}")[0][0]
- # ---------------------------------------------------------------- 未登录
- print("-- 未登录校验")
- for path, label in [("/api/user/info", "user/info"), ("/api/user/assets", "user/assets"),
- ("/api/myCoupon/list", "myCoupon/list"), ("/api/refund/list", "refund/list")]:
- r = get(path, token=None)
- check(f"未登录 {label} 返回 401", r.get("status") == 401, r.get("message"))
- # ---------------------------------------------------------------- 用户信息
- print("-- 用户中心")
- r = get("/api/user/info")
- info = field(r.get("data"), "userInfo") or {}
- check("user/info status=200", r.get("status") == 200, r.get("message"))
- check("user/info 手机号打码", info.get("mobile") == "185****1340", info.get("mobile"))
- check("user/info 含 avatarUrl / grade / balance 键",
- all(k in info for k in ("avatarUrl", "grade", "balance", "points")), list(info.keys()))
- check("user/info 隐藏 isDelete/storeId/createTime",
- not any(k in info for k in ("isDelete", "storeId", "createTime", "updateTime", "password")),
- [k for k in ("isDelete", "storeId", "createTime") if k in info])
- check("user/info 无 snake_case", not snake_keys(info), snake_keys(info)[:3])
- r = get("/api/user/assets")
- assets = field(r.get("data"), "assets") or {}
- check("user/assets 三个字段齐全", set(assets.keys()) == {"balance", "points", "coupon"}, assets.keys())
- check("user/assets balance 两位小数字符串",
- isinstance(assets.get("balance"), str) and re.fullmatch(r"-?\d+\.\d{2}", assets.get("balance", "")),
- assets.get("balance"))
- check("user/assets 无 snake_case", not snake_keys(assets), snake_keys(assets)[:3])
- # ---------------------------------------------------------------- 明细
- print("-- 余额 / 积分明细")
- r = get("/api/balance.log/list")
- logs = field(r.get("data"), "data") or []
- check("balance.log/list status=200", r.get("status") == 200, r.get("message"))
- check("balance.log/list 有数据且按时间倒序",
- len(logs) > 0 and (len(logs) < 2 or logs[0]["createTime"] >= logs[1]["createTime"]), len(logs))
- check("balance.log/list 隐藏 storeId 与 user",
- all("storeId" not in x and "user" not in x for x in logs[:3]), logs[:1])
- check("balance.log/list perPage=15", field(r.get("data"), "perPage") == 15, field(r.get("data"), "perPage"))
- r = get("/api/points.log/list")
- plogs = field(r.get("data"), "data") or []
- check("points.log/list status=200", r.get("status") == 200, r.get("message"))
- check("points.log/list 字段为 logId/value/describe",
- all({"logId", "value", "describe"} <= set(x.keys()) for x in plogs[:3]), plogs[:1])
- check("points.log/list 无 snake_case", not snake_keys(plogs), snake_keys(plogs)[:3])
- # ---------------------------------------------------------------- 修改资料
- print("-- 修改资料")
- r = post("/api/user/personal", {"form": {"nickName": "接口自测昵称"}})
- check("user/personal 修改成功", r.get("status") == 200 and r.get("message") == "恭喜您,信息修改成功", r)
- nick = db(f"SELECT nick_name, avatar_id FROM yoshop_user WHERE user_id={USER_ID}")[0]
- check("user/personal 昵称落库", nick[0] == "接口自测昵称", nick)
- check("user/personal 头像沿用原值", nick[1] == user_backup["avatar_id"], nick[1])
- # ---------------------------------------------------------------- 绑定手机号
- print("-- 绑定手机号")
- new_mobile = "19900002222"
- r = post("/api/user/bindMobile", {"form": {"mobile": new_mobile, "smsCode": "123456"}})
- check("bindMobile 无验证码时报错", r.get("status") == 500 and "短信验证码不存在" in (r.get("message") or ""), r)
- redis_set("yoshop:api:sms:" + new_mobile, json.dumps({"code": "654321", "times": 5}))
- r = post("/api/user/bindMobile", {"form": {"mobile": new_mobile, "smsCode": "000000"}})
- check("bindMobile 验证码错误时报错", r.get("status") == 500 and "验证码不正确" in (r.get("message") or ""), r)
- left = json.loads(db_redis("yoshop:api:sms:" + new_mobile) or "null")
- check("bindMobile 错误一次后剩余次数减一", left and left.get("times") == 4, left)
- r = post("/api/user/bindMobile", {"form": {"mobile": new_mobile, "smsCode": "654321"}})
- check("bindMobile 绑定成功", r.get("status") == 200 and r.get("message") == "恭喜您,手机号绑定成功", r)
- check("bindMobile 手机号落库", db(f"SELECT mobile FROM yoshop_user WHERE user_id={USER_ID}")[0][0] == new_mobile)
- check("bindMobile 验证码用后即焚", db_redis("yoshop:api:sms:" + new_mobile) is None)
- # 手机号已被占用
- temp_user_id = db_exec(
- "INSERT INTO yoshop_user (mobile, nick_name, avatar_id, gender, country, province, city, address_id, "
- "balance, points, pay_money, expend_money, grade_id, platform, last_login_time, is_delete, store_id, "
- "create_time, update_time) VALUES ('19900003333', '临时用户', 0, 0, '', '', '', 0, 0.00, 0, 0.00, "
- "0.00, 0, 'H5', 0, 0, 10001, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
- redis_set("yoshop:api:sms:19900003333", json.dumps({"code": "111111", "times": 5}))
- r = post("/api/user/bindMobile", {"form": {"mobile": "19900003333", "smsCode": "111111"}})
- check("bindMobile 手机号已占用时报错",
- r.get("status") == 500 and "已绑定其他账户" in (r.get("message") or ""), r)
- # ---------------------------------------------------------------- 我的优惠券
- print("-- 我的优惠券")
- r = post("/api/myCoupon/receive", {"couponId": 999999})
- check("myCoupon/receive 券不存在时报错",
- r.get("status") == 500 and "优惠券不存在" in (r.get("message") or ""), r)
- r = post("/api/myCoupon/receive", {"couponId": COUPON_ID})
- check("myCoupon/receive 领取成功", r.get("status") == 200 and r.get("message") == "领取成功", r)
- recv = db(f"SELECT user_coupon_id, name, is_use, is_expire, start_time, end_time "
- f"FROM yoshop_user_coupon WHERE user_id={USER_ID} AND coupon_id={COUPON_ID}")
- check("领取记录落库且未使用未过期", len(recv) == 1 and recv[0][2] == 0 and recv[0][3] == 0, recv)
- check("领取后有效期为 7 天", len(recv) == 1 and recv[0][5] - recv[0][4] == 7 * 86400, recv)
- check("券已领取数量 +1",
- db(f"SELECT receive_num FROM yoshop_coupon WHERE coupon_id={COUPON_ID}")[0][0]
- == coupon_receive_backup + 1)
- r = post("/api/myCoupon/receive", {"couponId": COUPON_ID})
- check("myCoupon/receive 重复领取被拒",
- r.get("status") == 500 and "已领取该优惠券" in (r.get("message") or ""), r)
- r = get("/api/myCoupon/list")
- data = field(r.get("data"), "data") or []
- check("myCoupon/list 含刚领取的券", any(x.get("couponId") == COUPON_ID for x in data), len(data))
- check("myCoupon/list 隐藏 storeId/createTime/updateTime",
- all(not {"storeId", "createTime", "updateTime"} & set(x.keys()) for x in data), data[:1])
- check("myCoupon/list state 为正常", data and field(data[0].get("state"), "text") == "正常", data[:1])
- check("myCoupon/list discount 为 0-10 小数", all(isinstance(x.get("discount"), (int, float)) for x in data),
- [x.get("discount") for x in data[:2]])
- r = get("/api/myCoupon/list", {"dataType": "isUsable"})
- usable = field(r.get("data"), "data") or []
- check("myCoupon/list isUsable 含该券", any(x.get("couponId") == COUPON_ID for x in usable), len(usable))
- r = get("/api/myCoupon/list", {"dataType": "isUsable", "amount": 50})
- filtered = field(r.get("data"), "data") or []
- check("myCoupon/list amount=50 过滤掉满 100 的券",
- not any(x.get("couponId") == COUPON_ID for x in filtered), len(filtered))
- r = get("/api/user/assets")
- check("user/assets 可用券数量 +1", field(field(r.get("data"), "assets"), "coupon") == 1,
- field(field(r.get("data"), "assets"), "coupon"))
- # ---------------------------------------------------------------- 售后
- print("-- 售后服务")
- order_id, order_goods_id = make_order()
- r = get("/api/refund/goods", {"orderGoodsId": order_goods_id})
- goods = field(r.get("data"), "goods") or {}
- check("refund/goods status=200", r.get("status") == 200, r.get("message"))
- check("refund/goods 含商品图与金额", "goodsImage" in goods and goods.get("totalPayPrice") == "80.00",
- {k: goods.get(k) for k in ("goodsImage", "totalPayPrice")})
- check("refund/goods 无 snake_case", not snake_keys(goods), snake_keys(goods)[:3])
- r = get("/api/refund/goods", {"orderGoodsId": 99999999})
- check("refund/goods 不存在时报错", r.get("status") == 500 and "未找到订单商品" in (r.get("message") or ""), r)
- r = post("/api/refund/apply", {"orderGoodsId": order_goods_id, "form": {"content": "不想要了"}})
- check("refund/apply 缺少类型时报错", r.get("status") == 500 and "请选择售后类型" in (r.get("message") or ""), r)
- r = post("/api/refund/apply", {"orderGoodsId": order_goods_id,
- "form": {"type": 10, "content": "接口自测:商品有瑕疵"}})
- check("refund/apply 提交成功", r.get("status") == 200 and r.get("message") == "提交成功", r)
- refund_rows = db(f"SELECT order_refund_id, type, apply_desc, audit_status, status, is_user_send "
- f"FROM yoshop_order_refund WHERE order_goods_id={order_goods_id}")
- check("售后单落库为待审核", len(refund_rows) == 1 and refund_rows[0][3] == 0 and refund_rows[0][4] == 0,
- refund_rows)
- refund_id = refund_rows[0][0]
- created_refunds.append(refund_id)
- r = get("/api/refund/goods", {"orderGoodsId": order_goods_id})
- check("已申请售后不可重复申请", r.get("status") == 500 and "已申请售后" in (r.get("message") or ""), r)
- r = get("/api/refund/detail", {"orderRefundId": refund_id})
- detail = field(r.get("data"), "detail") or {}
- check("refund/detail stateText=等待审核中", detail.get("stateText") == "等待审核中", detail.get("stateText"))
- check("refund/detail 含 orderGoods 且图片为空数组",
- isinstance(detail.get("orderGoods"), dict) and detail.get("images") == [], detail.get("images"))
- check("refund/detail 隐藏 storeId/updateTime",
- not {"storeId", "updateTime"} & set(detail.keys()), [k for k in ("storeId", "updateTime") if k in detail])
- check("refund/detail 无 snake_case", not snake_keys(detail), snake_keys(detail)[:3])
- r = get("/api/refund/list", {"state": 0})
- refund_list = field(r.get("data"), "data") or []
- check("refund/list 含该售后单", any(x.get("orderRefundId") == refund_id for x in refund_list), len(refund_list))
- check("refund/list 每项均带 stateText", all(x.get("stateText") for x in refund_list), refund_list[:1])
- check("refund/list 隐藏 storeId/updateTime/images",
- all(not {"storeId", "updateTime", "images"} & set(x.keys()) for x in refund_list), refund_list[:1])
- r = get("/api/refund/list", {"state": 20})
- check("refund/list state=20 不含进行中的单",
- not any(x.get("orderRefundId") == refund_id for x in (field(r.get("data"), "data") or [])), r.get("data"))
- # 未审核不允许发货
- r = post("/api/refund/delivery", {"orderRefundId": refund_id,
- "form": {"expressId": EXPRESS_ID, "expressNo": "SF0001"}})
- check("refund/delivery 未审核时报错", r.get("status") == 500 and "不合法" in (r.get("message") or ""), r)
- db_exec(f"UPDATE yoshop_order_refund SET audit_status=10 WHERE order_refund_id={refund_id}")
- r = post("/api/refund/delivery", {"orderRefundId": refund_id, "form": {"expressNo": "SF0001"}})
- check("refund/delivery 未选物流公司时报错",
- r.get("status") == 500 and "请选择物流公司" in (r.get("message") or ""), r)
- r = post("/api/refund/delivery", {"orderRefundId": refund_id, "form": {"expressId": EXPRESS_ID}})
- check("refund/delivery 未填单号时报错",
- r.get("status") == 500 and "请填写物流单号" in (r.get("message") or ""), r)
- r = post("/api/refund/delivery", {"orderRefundId": refund_id,
- "form": {"expressId": EXPRESS_ID, "expressNo": "SF0001"}})
- check("refund/delivery 发货成功", r.get("status") == 200 and r.get("message") == "操作成功", r)
- sent = db(f"SELECT is_user_send, express_id, express_no, send_time FROM yoshop_order_refund "
- f"WHERE order_refund_id={refund_id}")[0]
- check("售后单发货信息落库",
- sent[0] == 1 and str(sent[1]) == str(EXPRESS_ID) and sent[2] == "SF0001" and sent[3] > 0, sent)
- r = get("/api/refund/detail", {"orderRefundId": refund_id})
- detail = field(r.get("data"), "detail") or {}
- check("refund/detail 发货后 stateText 变化",
- detail.get("stateText") == "已发货,待平台确认", detail.get("stateText"))
- check("refund/detail 带物流公司名称",
- field(detail.get("express"), "expressName") == "中通快递", detail.get("express"))
- r = get("/api/refund/detail", {"orderRefundId": 99999999})
- check("refund/detail 不存在时报错", r.get("status") == 500 and "未找到该售后单" in (r.get("message") or ""), r)
- # ---------------------------------------------------------------- 充值
- print("-- 余额充值")
- r = get("/api/recharge/center", {"client": "MP-WEIXIN"})
- check("recharge/center 无支付方式时报错",
- r.get("status") == 500 and "没有可用的支付方式" in (r.get("message") or ""), r)
- make_payment("MP-WEIXIN", "wechat", 1)
- make_payment("MP-WEIXIN", "balance", 0)
- r = get("/api/recharge/center", {"client": "MP-WEIXIN"})
- center = r.get("data") or {}
- setting = field(center, "setting") or {}
- check("recharge/center status=200", r.get("status") == 200, r.get("message"))
- check("recharge/center 设置默认值(isCustom=1 / lowestMoney=10 / isMatchPlan=1)",
- setting.get("isCustom") == 1 and float(setting.get("lowestMoney")) == 10.0
- and setting.get("isMatchPlan") == 1, setting)
- check("recharge/center personal 为当前用户",
- field(field(center, "personal"), "userId") == USER_ID, field(center, "personal"))
- check("recharge/center planList 含套餐", any(p.get("planId") == PLAN_ID for p in
- (field(center, "planList") or [])), field(center, "planList"))
- check("recharge/center paymentMethods 排除余额支付",
- [m.get("method") for m in (field(center, "paymentMethods") or [])] == ["wechat"],
- field(center, "paymentMethods"))
- check("recharge/center 无 snake_case", not snake_keys(center), snake_keys(center)[:3])
- r = post("/api/recharge/submit", {"form": {"method": "balance", "client": "MP-WEIXIN"}})
- check("recharge/submit 缺套餐与金额时报错",
- r.get("status") == 500 and "请选择充值套餐或输入充值金额" in (r.get("message") or ""), r)
- r = post("/api/recharge/submit", {"form": {"method": "balance", "client": "MP-WEIXIN",
- "customMoney": "5"}})
- check("recharge/submit 低于最低金额时报错",
- r.get("status") == 500 and "最低充值金额不能低于10元" in (r.get("message") or ""), r)
- r = post("/api/recharge/submit", {"form": {"method": "balance", "client": "MP-WEIXIN",
- "customMoney": "100"}})
- check("recharge/submit 自定义金额下单成功",
- r.get("status") == 200 and r.get("message") == "下单成功" and field(r.get("data"), "payment") == [], r)
- row = db(f"SELECT order_id, order_no, recharge_type, plan_id, pay_price, gift_money, actual_money, "
- f"pay_status, platform FROM yoshop_recharge_order WHERE user_id={USER_ID} ORDER BY order_id DESC "
- "LIMIT 1")[0]
- created_recharge_orders.append(row[0])
- check("充值订单号以 RC 开头且 18 位以上", str(row[1]).startswith("RC") and len(str(row[1])) >= 18, row[1])
- check("自定义充值匹配套餐(赠 10)",
- row[2] == 10 and row[3] == PLAN_ID and float(row[5]) == 10.0 and float(row[6]) == 110.0, row)
- check("充值订单平台取请求头", row[8] == "MP-WEIXIN", row[8])
- snapshot = db(f"SELECT plan_id, plan_name, money, gift_money FROM yoshop_recharge_order_plan "
- f"WHERE order_id={row[0]}")
- check("套餐快照落库", len(snapshot) == 1 and snapshot[0][0] == PLAN_ID, snapshot)
- r = post("/api/recharge/submit", {"form": {"method": "balance", "client": "MP-WEIXIN",
- "planId": PLAN_ID}})
- check("recharge/submit 指定套餐下单成功", r.get("status") == 200, r)
- row2 = db(f"SELECT order_id, recharge_type, plan_id, pay_price, gift_money, actual_money "
- f"FROM yoshop_recharge_order WHERE user_id={USER_ID} ORDER BY order_id DESC LIMIT 1")[0]
- created_recharge_orders.append(row2[0])
- check("套餐充值金额与赠送正确",
- row2[1] == 20 and float(row2[3]) == 100.0 and float(row2[4]) == 10.0 and float(row2[5]) == 110.0, row2)
- r = post("/api/recharge/submit", {"form": {"method": "wechat", "client": "MP-WEIXIN",
- "planId": PLAN_ID}})
- check("recharge/submit 微信支付未接入时明确报错",
- r.get("status") == 500 and "第三方支付" in (r.get("message") or ""), r)
- r = get("/api/recharge/tradeQuery", {"outTradeNo": "RC123456", "method": "wechat",
- "client": "MP-WEIXIN"})
- check("recharge/tradeQuery 未支付返回 isPay=false",
- r.get("status") == 200 and field(r.get("data"), "isPay") is False, r)
- check("recharge/tradeQuery 提示未支付",
- "订单未支付" in (r.get("message") or ""), r.get("message"))
- db_exec(f"UPDATE yoshop_recharge_order SET pay_status=20, pay_time=UNIX_TIMESTAMP(), pay_method='balance' "
- f"WHERE order_id={row[0]}")
- r = get("/api/recharge.order/list")
- orders = field(r.get("data"), "data") or []
- check("recharge.order/list 只列已支付订单",
- [o.get("orderId") for o in orders] == [row[0]], [o.get("orderId") for o in orders])
- check("recharge.order/list 隐藏 tradeId/createTime/storeId",
- all(not {"tradeId", "createTime", "storeId"} & set(o.keys()) for o in orders), orders[:1])
- check("recharge.order/list 金额为两位小数字符串",
- orders and orders[0].get("actualMoney") == "110.00", orders[:1])
- check("recharge.order/list 无 snake_case", not snake_keys(orders), snake_keys(orders)[:3])
- # ---------------------------------------------------------------- 上传
- print("-- 文件上传")
- r = upload("/api/upload/image", token=None, check_login=1)
- check("upload/image 需要登录时未登录返回 401", r.get("status") == 401, r)
- r = upload("/api/upload/image", check_login=1)
- file_info = field(r.get("data"), "fileInfo") or {}
- check("upload/image 上传成功", r.get("status") == 200 and r.get("message") == "文件上传成功", r)
- check("upload/image 返回 fileId 与 previewUrl",
- isinstance(file_info.get("fileId"), int) and str(file_info.get("previewUrl", "")).startswith("http"),
- file_info)
- check("upload/image 隐藏 filePath/fileName/storeId",
- not {"filePath", "fileName", "storeId", "uploaderId"} & set(file_info.keys()), list(file_info.keys()))
- if file_info.get("fileId"):
- created_files.append(file_info["fileId"])
- uploader = db(f"SELECT channel, uploader_id, file_type, is_delete FROM yoshop_upload_file "
- f"WHERE file_id={file_info['fileId']}")[0]
- check("上传记录 channel=20 且记录上传者", uploader[0] == 20 and uploader[1] == USER_ID, uploader)
- r = upload("/api/upload/image", token=None, check_login=0)
- anon_info = field(r.get("data"), "fileInfo") or {}
- check("upload/image checkLogin=0 允许匿名上传", r.get("status") == 200, r)
- if anon_info.get("fileId"):
- created_files.append(anon_info["fileId"])
- check("匿名上传 uploader_id=0",
- db(f"SELECT uploader_id FROM yoshop_upload_file WHERE file_id={anon_info['fileId']}")[0][0] == 0)
- # 用上传的文件改头像
- if file_info.get("fileId"):
- r = post("/api/user/personal", {"form": {"avatarId": file_info["fileId"]}})
- check("user/personal 设置头像成功", r.get("status") == 200, r)
- r = get("/api/user/info")
- avatar_url = field(field(r.get("data"), "userInfo"), "avatarUrl")
- check("user/info 头像地址随之上线", isinstance(avatar_url, str) and avatar_url.startswith("http"),
- avatar_url)
- check("user/info avatar 对象非空", field(field(r.get("data"), "userInfo"), "avatar") is not None)
- print(f"\n结果:PASS {passed} / FAIL {failed}")
- return 1 if failed else 0
- def db_redis(key):
- sock = socket.create_connection(("192.168.1.177", 6379), timeout=5)
- try:
- cmd(sock, "AUTH", REDIS_PWD)
- result = cmd(sock, "GET", key)
- finally:
- sock.close()
- return result if isinstance(result, str) else None
- if __name__ == "__main__":
- try:
- code = main()
- finally:
- cleanup()
- sys.exit(code)
|