| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- # -*- coding: utf-8 -*-
- """营销与会员写接口端到端测试(优惠券 / 充值套餐 / 会员等级 / 会员充值 / 改等级 / 删除会员 / 领取记录)。
- 自带造数与清理:测试记录以 TESTJ 命名,跑完物理删除;用户余额、积分、等级恢复原值。
- 用法:python test_market_writes.py
- """
- import json
- import re
- from pathlib import Path
- import pymysql
- import requests
- from _root import ROOT
- JAVA = "http://127.0.0.1:18080"
- TOKEN = "testsession123"
- STORE_ID = 10001
- ROOT = Path(str(ROOT))
- YML = ROOT / "yoshop-java" / "src" / "main" / "resources" / "application.yml"
- SESSION = requests.Session()
- SESSION.trust_env = False
- PASS = FAIL = 0
- FAILS = []
- def db():
- pwd = re.findall(r"password: *(.+)", YML.read_text(encoding="utf-8"))[0].strip().strip('"')
- return pymysql.connect(host="192.168.1.177", port=3306, user="root", password=pwd,
- database="shop", autocommit=True)
- CONN = db()
- def query(sql, args=None):
- cur = CONN.cursor()
- cur.execute(sql, args or ())
- cols = [d[0] for d in cur.description]
- return [dict(zip(cols, row)) for row in cur.fetchall()]
- def execute(sql, args=None):
- cur = CONN.cursor()
- cur.execute(sql, args or ())
- return cur.rowcount
- def get(path, params=None):
- r = SESSION.get(JAVA + path, headers={"Access-Token": TOKEN}, params=params, timeout=60)
- return r.json()
- def post(path, body):
- r = SESSION.post(JAVA + path, headers={"Access-Token": TOKEN}, json=body, timeout=60)
- return r.json()
- def check(name, ok, detail=""):
- global PASS, FAIL
- if ok:
- PASS += 1
- print(f" [ok] {name}")
- else:
- FAIL += 1
- FAILS.append(name)
- print(f" [FAIL] {name} {detail}")
- def eq(name, actual, expect):
- check(name, actual == expect, f"期望 {expect!r} 实际 {actual!r}")
- def main():
- # ---------------------------------------------------------------- 基线
- users = query("select user_id, balance, points, grade_id from yoshop_user where user_id=10001")
- if not users:
- print("缺少测试会员 10001,终止")
- return
- base = users[0]
- base_balance = base["balance"]
- max_balance_log = (query("select coalesce(max(log_id),0) m from yoshop_user_balance_log")[0]["m"])
- max_points_log = (query("select coalesce(max(log_id),0) m from yoshop_user_points_log")[0]["m"])
- max_grade_log = (query("select coalesce(max(log_id),0) m from yoshop_user_grade_log")[0]["m"])
- print(f"基线:余额 {base_balance} 积分 {base['points']} 等级 {base['grade_id']}")
- coupon_ids = []
- plan_ids = []
- grade_ids = []
- test_user = 10999
- try:
- # ------------------------------------------------------------ 优惠券
- print("\n[优惠券] 新增(满减 · 领取后生效 · 全部商品)")
- res = post("/store/market.coupon/add", {"form": {
- "name": "TESTJ券A", "coupon_type": 10, "reduce_price": 10, "min_price": 100,
- "expire_type": 10, "expire_day": 7, "apply_range": 10, "total_num": -1,
- "status": 1, "describe": "自动化用例", "sort": 100}})
- eq("add 返回成功", res.get("status"), 200)
- eq("add 文案", res.get("message"), "添加成功")
- rows = query("select * from yoshop_coupon where name='TESTJ券A'")
- check("库内已写入", len(rows) == 1)
- a = rows[0]
- coupon_ids.append(a["coupon_id"])
- eq("start_time 清零", a["start_time"], 0)
- eq("end_time 清零", a["end_time"], 0)
- eq("expire_day 保留", a["expire_day"], 7)
- eq("apply_range_config 空数组", a["apply_range_config"], "[]")
- eq("store_id", a["store_id"], STORE_ID)
- eq("reduce_price", str(a["reduce_price"]), "10.00")
- check("create_time 已写", a["create_time"] > 0)
- detail = get("/store/market.coupon/detail", {"couponId": a["coupon_id"]})["data"]["detail"]
- eq("detail 折扣为 0", detail["discount"], 0)
- eq("detail 开始时间为 null", detail["start_time"], None)
- eq("detail 适用范围空数组", detail["apply_range_config"], [])
- eq("detail state 正常", detail["state"], {"text": "正常", "value": 1})
- print("\n[优惠券] 新增(折扣 · 固定时间 · 指定商品)")
- res = post("/store/market.coupon/add", {"form": {
- "name": "TESTJ券B", "coupon_type": 20, "discount": 9.9, "min_price": 50,
- "expire_type": 20, "betweenTime": ["2026-10-01", "2026-10-31"],
- "apply_range": 20, "apply_range_config": {"goodsIds": ["10004"]},
- "total_num": 5, "status": 1, "describe": "", "sort": 90}})
- eq("add 返回成功", res.get("status"), 200)
- rows = query("select * from yoshop_coupon where name='TESTJ券B'")
- check("库内已写入", len(rows) == 1)
- b = rows[0]
- coupon_ids.append(b["coupon_id"])
- eq("discount 乘 10 入库", b["discount"], 99)
- eq("expire_day 置 1", b["expire_day"], 1)
- eq("apply_range_config 存对象", json.loads(b["apply_range_config"]), {"goodsIds": ["10004"]})
- check("end_time 补足当天 23:59:59", (b["end_time"] - b["start_time"]) % 86400 == 86399)
- detail = get("/store/market.coupon/detail", {"couponId": b["coupon_id"]})["data"]["detail"]
- eq("detail 折扣率 9.9", detail["discount"], 9.9)
- eq("detail 开始时间 Y/m/d", detail["start_time"], "2026/10/01")
- eq("detail 结束时间 Y/m/d", detail["end_time"], "2026/10/31")
- print("\n[优惠券] 固定时间缺少区间")
- res = post("/store/market.coupon/add", {"form": {
- "name": "TESTJ券C", "coupon_type": 10, "reduce_price": 5, "min_price": 10,
- "expire_type": 20, "apply_range": 10, "total_num": -1, "status": 1, "sort": 100}})
- eq("返回失败", res.get("status"), 500)
- eq("提示有效期", res.get("message"), "请选择有效期范围")
- eq("未写入脏数据", len(query("select coupon_id from yoshop_coupon where name='TESTJ券C'")), 0)
- print("\n[优惠券] 编辑")
- res = post("/store/market.coupon/edit", {"couponId": a["coupon_id"], "form": {
- "name": "TESTJ券A改", "coupon_type": 10, "reduce_price": 20, "min_price": 200,
- "expire_type": 10, "expire_day": 3, "apply_range": 20,
- "apply_range_config": {"goodsIds": ["10004", "10005"]},
- "total_num": 100, "status": 1, "describe": "改过", "sort": 88}})
- eq("edit 返回成功", res.get("status"), 200)
- eq("edit 文案", res.get("message"), "更新成功")
- a2 = query("select * from yoshop_coupon where coupon_id=%s", (a["coupon_id"],))[0]
- eq("名称已改", a2["name"], "TESTJ券A改")
- eq("减免金额已改", str(a2["reduce_price"]), "20.00")
- eq("适用范围已改", a2["apply_range"], 20)
- eq("适用范围配置已改", json.loads(a2["apply_range_config"]), {"goodsIds": ["10004", "10005"]})
- eq("排序已改", a2["sort"], 88)
- print("\n[优惠券] 编辑为全部商品 → 配置清空")
- post("/store/market.coupon/edit", {"couponId": a["coupon_id"], "form": {
- "name": "TESTJ券A改", "coupon_type": 10, "reduce_price": 20, "min_price": 200,
- "expire_type": 10, "expire_day": 3, "apply_range": 10,
- "apply_range_config": {"goodsIds": ["10004"]},
- "total_num": 100, "status": 1, "describe": "改过", "sort": 88}})
- a3 = query("select apply_range_config from yoshop_coupon where coupon_id=%s", (a["coupon_id"],))[0]
- eq("配置被清空", a3["apply_range_config"], "[]")
- print("\n[优惠券] 编辑不存在的券")
- res = post("/store/market.coupon/edit", {"couponId": 999999, "form": {"name": "x"}})
- eq("返回失败", res.get("status"), 500)
- print("\n[优惠券] 删除")
- res = post("/store/market.coupon/delete", {"couponId": a["coupon_id"]})
- eq("delete 返回成功", res.get("status"), 200)
- eq("软删除标记", query("select is_delete from yoshop_coupon where coupon_id=%s",
- (a["coupon_id"],))[0]["is_delete"], 1)
- listed = get("/store/market.coupon/list", {"page": 1, "search": "TESTJ券A"})["data"]["list"]["data"]
- eq("列表不再出现", len(listed), 0)
- print("\n[优惠券] 删除不存在的券")
- eq("返回失败", post("/store/market.coupon/delete", {"couponId": 999999}).get("status"), 500)
- # ---------------------------------------------------------- 充值套餐
- print("\n[充值套餐] 新增 / 编辑 / 删除")
- res = post("/store/market.recharge.plan/add", {"form": {
- "plan_name": "TESTJ套餐A", "money": 100, "gift_money": 10, "sort": 100}})
- eq("add 返回成功", res.get("status"), 200)
- rows = query("select * from yoshop_recharge_plan where plan_name='TESTJ套餐A'")
- check("库内已写入", len(rows) == 1)
- plan = rows[0]
- plan_ids.append(plan["plan_id"])
- eq("充值金额", str(plan["money"]), "100.00")
- eq("赠送金额", str(plan["gift_money"]), "10.00")
- eq("store_id", plan["store_id"], STORE_ID)
- res = post("/store/market.recharge.plan/edit", {"planId": plan["plan_id"], "form": {
- "plan_name": "TESTJ套餐A改", "money": 200, "gift_money": 30, "sort": 66}})
- eq("edit 返回成功", res.get("status"), 200)
- plan2 = query("select * from yoshop_recharge_plan where plan_id=%s", (plan["plan_id"],))[0]
- eq("名称已改", plan2["plan_name"], "TESTJ套餐A改")
- eq("金额已改", str(plan2["money"]), "200.00")
- eq("排序已改", plan2["sort"], 66)
- res = post("/store/market.recharge.plan/delete", {"planId": plan["plan_id"]})
- eq("delete 返回成功", res.get("status"), 200)
- eq("软删除标记", query("select is_delete from yoshop_recharge_plan where plan_id=%s",
- (plan["plan_id"],))[0]["is_delete"], 1)
- eq("删除不存在的套餐", post("/store/market.recharge.plan/delete", {"planId": 999999}).get("status"), 500)
- # ---------------------------------------------------------- 会员等级
- print("\n[会员等级] 新增 / 权重重复 / 编辑 / 删除")
- res = post("/store/user.grade/add", {"form": {
- "name": "TESTJ等级", "weight": 9,
- "upgrade": {"expend_money": 50000}, "equity": {"discount": "9.5"}, "status": 1}})
- eq("add 返回成功", res.get("status"), 200)
- rows = query("select * from yoshop_user_grade where name='TESTJ等级'")
- check("库内已写入", len(rows) == 1)
- grade = rows[0]
- grade_ids.append(grade["grade_id"])
- eq("权重", grade["weight"], 9)
- eq("升级条件 JSON", json.loads(grade["upgrade"]), {"expend_money": 50000})
- eq("等级权益 JSON", json.loads(grade["equity"]), {"discount": "9.5"})
- eq("store_id", grade["store_id"], STORE_ID)
- res = post("/store/user.grade/add", {"form": {"name": "TESTJ等级2", "weight": 9,
- "upgrade": {}, "equity": {}, "status": 1}})
- eq("权重重复被拦", res.get("status"), 500)
- eq("权重重复提示", res.get("message"), "等级权重已存在")
- res = post("/store/user.grade/edit", {"gradeId": grade["grade_id"], "form": {
- "name": "TESTJ等级改", "weight": 8,
- "upgrade": {"expend_money": 60000}, "equity": {"discount": "9.4"}, "status": 1}})
- eq("edit 返回成功", res.get("status"), 200)
- g2 = query("select * from yoshop_user_grade where grade_id=%s", (grade["grade_id"],))[0]
- eq("名称已改", g2["name"], "TESTJ等级改")
- eq("权重已改", g2["weight"], 8)
- res = post("/store/user.grade/edit", {"gradeId": grade["grade_id"], "form": {
- "name": "TESTJ等级改", "weight": 1,
- "upgrade": {"expend_money": 60000}, "equity": {"discount": "9.4"}, "status": 1}})
- eq("编辑撞已有权重被拦", res.get("status"), 500)
- # 有会员占用时不允许删除:先造一个挂在该等级下的会员
- execute("delete from yoshop_user where user_id=%s", (test_user,))
- execute("""insert into yoshop_user
- (user_id,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 (%s,'19900000000','TESTJ会员',0,0,'','','',0,0,0,0,0,%s,'H5',0,0,%s,0,0)""",
- (test_user, grade["grade_id"], STORE_ID))
- res = post("/store/user.grade/delete", {"gradeId": grade["grade_id"]})
- eq("等级下有会员时禁止删除", res.get("status"), 500)
- eq("提示文案", res.get("message"), "该会员等级下存在用户,不允许删除")
- eq("未被删除", query("select is_delete from yoshop_user_grade where grade_id=%s",
- (grade["grade_id"],))[0]["is_delete"], 0)
- execute("update yoshop_user set grade_id=0 where user_id=%s", (test_user,))
- res = post("/store/user.grade/delete", {"gradeId": grade["grade_id"]})
- eq("无会员时可删除", res.get("status"), 200)
- eq("软删除标记", query("select is_delete from yoshop_user_grade where grade_id=%s",
- (grade["grade_id"],))[0]["is_delete"], 1)
- # ------------------------------------------------------------ 会员充值
- print("\n[会员充值] 余额 · 增加")
- before = query("select balance from yoshop_user where user_id=10001")[0]["balance"]
- res = post("/store/user/recharge", {"userId": 10001, "target": "balance",
- "form": {"balance": {"mode": "inc", "money": 50, "remark": "TESTJ充值"}}})
- eq("返回成功", res.get("status"), 200)
- eq("文案", res.get("message"), "操作成功")
- after = query("select balance from yoshop_user where user_id=10001")[0]["balance"]
- eq("余额增加 50", str(after - before), "50.00")
- logs = query("""select * from yoshop_user_balance_log where user_id=10001
- and log_id>%s order by log_id desc limit 1""", (max_balance_log,))
- eq("写入余额流水", len(logs), 1)
- eq("流水场景=管理员操作", logs[0]["scene"], 30)
- eq("流水金额", str(logs[0]["money"]), "50.00")
- check("流水描述含管理员", logs[0]["describe"].startswith("后台管理员 [")
- and logs[0]["describe"].endswith("] 操作"), logs[0]["describe"])
- eq("流水备注", logs[0]["remark"], "TESTJ充值")
- print("\n[会员充值] 余额 · 减少")
- before = query("select balance from yoshop_user where user_id=10001")[0]["balance"]
- res = post("/store/user/recharge", {"userId": 10001, "target": "balance",
- "form": {"balance": {"mode": "dec", "money": 20, "remark": ""}}})
- eq("返回成功", res.get("status"), 200)
- after = query("select balance from yoshop_user where user_id=10001")[0]["balance"]
- eq("余额减少 20", str(before - after), "20.00")
- logs = query("""select money from yoshop_user_balance_log where user_id=10001
- and log_id>%s order by log_id desc limit 1""", (max_balance_log,))
- eq("流水记录负数", str(logs[0]["money"]), "-20.00")
- print("\n[会员充值] 余额 · 最终金额")
- res = post("/store/user/recharge", {"userId": 10001, "target": "balance",
- "form": {"balance": {"mode": "final", "money": 888, "remark": ""}}})
- eq("返回成功", res.get("status"), 200)
- eq("余额变为 888", str(query("select balance from yoshop_user where user_id=10001")[0]["balance"]), "888.00")
- print("\n[会员充值] 金额非法")
- res = post("/store/user/recharge", {"userId": 10001, "target": "balance",
- "form": {"balance": {"mode": "inc", "money": "", "remark": ""}}})
- eq("空金额被拦", res.get("status"), 500)
- eq("提示文案", res.get("message"), "请输入正确的金额")
- res = post("/store/user/recharge", {"userId": 10001, "target": "unknown", "form": {}})
- eq("非法 target 被拦", res.get("status"), 500)
- print("\n[会员充值] 积分 · 增加 / 最终值")
- res = post("/store/user/recharge", {"userId": 10001, "target": "points",
- "form": {"points": {"mode": "inc", "value": 30, "remark": "TESTJ积分"}}})
- eq("返回成功", res.get("status"), 200)
- eq("积分 +30", query("select points from yoshop_user where user_id=10001")[0]["points"], 30)
- logs = query("""select * from yoshop_user_points_log where user_id=10001
- and log_id>%s order by log_id desc limit 1""", (max_points_log,))
- eq("写入积分流水", len(logs), 1)
- eq("流水积分值", logs[0]["value"], 30)
- eq("流水描述", logs[0]["describe"].startswith("后台管理员 ["), True)
- res = post("/store/user/recharge", {"userId": 10001, "target": "points",
- "form": {"points": {"mode": "final", "value": 0, "remark": ""}}})
- eq("返回成功", res.get("status"), 200)
- eq("积分归零", query("select points from yoshop_user where user_id=10001")[0]["points"], 0)
- res = post("/store/user/recharge", {"userId": 10001, "target": "points",
- "form": {"points": {"mode": "inc", "value": "", "remark": ""}}})
- eq("空积分被拦", res.get("status"), 500)
- eq("提示文案", res.get("message"), "请输入正确的积分数量")
- # ------------------------------------------------------------ 改等级
- print("\n[会员等级变更]")
- res = post("/store/user/grade", {"userId": 10001, "form": {"grade_id": 10002, "remark": "TESTJ调级"}})
- eq("返回成功", res.get("status"), 200)
- eq("等级已改", query("select grade_id from yoshop_user where user_id=10001")[0]["grade_id"], 10002)
- glog = query("""select * from yoshop_user_grade_log where user_id=10001
- and log_id>%s order by log_id desc limit 1""", (max_grade_log,))
- eq("写入等级变更记录", len(glog), 1)
- eq("变更前等级", glog[0]["old_grade_id"], 0)
- eq("变更后等级", glog[0]["new_grade_id"], 10002)
- eq("变更类型=管理员", glog[0]["change_type"], 10)
- eq("变更备注", glog[0]["remark"], "TESTJ调级")
- post("/store/user/grade", {"userId": 10001, "form": {"grade_id": 0, "remark": ""}})
- eq("已恢复原等级", query("select grade_id from yoshop_user where user_id=10001")[0]["grade_id"], 0)
- # ------------------------------------------------------------ 删除会员
- print("\n[删除会员]")
- execute("""insert into yoshop_user_oauth (user_id,oauth_type,oauth_id,unionid,store_id,is_delete,
- create_time,update_time) values (%s,'TESTJ','oauth-test','',%s,0,0,0)""",
- (test_user, STORE_ID))
- res = post("/store/user/delete", {"userId": test_user})
- eq("返回成功", res.get("status"), 200)
- eq("文案", res.get("message"), "删除成功")
- eq("会员标记删除", query("select is_delete from yoshop_user where user_id=%s",
- (test_user,))[0]["is_delete"], 1)
- eq("三方绑定同步标记", query("select is_delete from yoshop_user_oauth where user_id=%s",
- (test_user,))[0]["is_delete"], 1)
- eq("重复删除被拦", post("/store/user/delete", {"userId": test_user}).get("status"), 500)
- # -------------------------------------------------------- 领取记录列表
- print("\n[领取记录] 造一条领取记录后读取")
- execute("""insert into yoshop_user_coupon
- (coupon_id,name,coupon_type,reduce_price,discount,min_price,expire_type,expire_day,
- start_time,end_time,apply_range,apply_range_config,`describe`,is_expire,is_use,
- user_id,store_id,create_time,update_time)
- values (10002,'TESTJ领取券',20,0.00,99,50.00,20,1,1790784000,1793462399,10,'[]','',
- 0,0,10001,%s,1792000000,1792000000)""", (STORE_ID,))
- uc = query("select user_coupon_id from yoshop_user_coupon where name='TESTJ领取券'")[0]["user_coupon_id"]
- res = get("/store/market.coupon/receive", {"page": 1, "couponName": "TESTJ领取券"})
- data = res["data"]["list"]
- eq("总数", data["total"], 1)
- row = data["data"][0]
- eq("折扣率 9.9", row["discount"], 9.9)
- eq("开始时间 Y/m/d", row["start_time"], "2026/10/01")
- eq("state 正常", row["state"], {"text": "正常", "value": 1})
- check("带会员信息", isinstance(row.get("user"), dict) and row["user"]["user_id"] == 10001)
- check("会员含头像键", "avatar" in row["user"] and "avatar_url" in row["user"])
- check("会员无 grade 键", "grade" not in row["user"])
- res = get("/store/market.coupon/receive", {"page": 1, "nickName": "185****1340"})
- check("按昵称可检索", res["data"]["list"]["total"] >= 1)
- res = get("/store/market.coupon/receive", {"page": 1, "nickName": "不存在的昵称XYZ"})
- eq("昵称无匹配为空", res["data"]["list"]["total"], 0)
- finally:
- # ------------------------------------------------------------ 清理
- # 只删本次新建的记录(本地存在与线上对照的种子数据,不能按名称前缀删)
- print("\n[清理]")
- if coupon_ids:
- execute("delete from yoshop_coupon where coupon_id in (%s)"
- % ",".join(str(int(i)) for i in coupon_ids))
- if plan_ids:
- execute("delete from yoshop_recharge_plan where plan_id in (%s)"
- % ",".join(str(int(i)) for i in plan_ids))
- if grade_ids:
- execute("delete from yoshop_user_grade where grade_id in (%s)"
- % ",".join(str(int(i)) for i in grade_ids))
- execute("delete from yoshop_user_coupon where name='TESTJ领取券'")
- execute("delete from yoshop_user_balance_log where log_id>%s", (max_balance_log,))
- execute("delete from yoshop_user_points_log where log_id>%s", (max_points_log,))
- execute("delete from yoshop_user_grade_log where log_id>%s", (max_grade_log,))
- execute("delete from yoshop_user_oauth where user_id=%s", (test_user,))
- execute("delete from yoshop_user where user_id=%s", (test_user,))
- execute("update yoshop_user set balance=%s, points=%s, grade_id=%s where user_id=10001",
- (base_balance, base["points"], base["grade_id"]))
- restored = query("select balance, points, grade_id from yoshop_user where user_id=10001")[0]
- eq("余额已恢复", str(restored["balance"]), str(base_balance))
- eq("积分已恢复", restored["points"], base["points"])
- eq("等级已恢复", restored["grade_id"], base["grade_id"])
- print(f"\n通过 {PASS} / 失败 {FAIL}")
- if FAILS:
- print("失败项:" + "、".join(FAILS))
- if __name__ == "__main__":
- main()
|