test_api_batch2.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. # -*- coding: utf-8 -*-
  2. """客户端 api 第 2 批接口冒烟:购物车 / 结算下单 / 订单 / 订单评价 / 收货地址。
  3. 用法:起 Java 18080 后执行
  4. python tools/test_api_batch2.py
  5. 环境变量:YOSHOP_API_BASE(默认 http://127.0.0.1:18080)
  6. 说明:
  7. - 全部是需登录接口,脚本会往 Redis 写一个测试 token(yoshop:api:token:apitest-batch2);
  8. - 会真实下单,finally 中删除测试订单/订单商品/订单地址/评价并恢复库存与默认地址;
  9. - 数据库为联合调试库,测试商品固定取 10009(运费模板 10002,上海市 3 元运费)。
  10. """
  11. import json
  12. import os
  13. import re
  14. import socket
  15. import sys
  16. import pymysql
  17. import requests
  18. from _root import ROOT
  19. from redis_probe import cmd
  20. BASE = os.environ.get("YOSHOP_API_BASE", "http://127.0.0.1:18080")
  21. TOKEN = "apitest-batch2"
  22. USER_ID = 10001
  23. ADDRESS_ID = 10001
  24. # 测试商品:多规格、运费模板 10002(上海 +3 元、续件 +3 元)
  25. GOODS_ID = 10009
  26. SKU_ID = "10009"
  27. SKU_PRICE = "80.00"
  28. # 单规格包邮商品
  29. FREE_GOODS_ID = 10007
  30. FREE_SKU_ID = "0"
  31. FREE_SKU_PRICE = "39.90"
  32. YML = str(ROOT / "yoshop-java/src/main/resources/application.yml")
  33. TEXT = open(YML, encoding="utf-8").read()
  34. DB_PWD = re.findall(r"password:\s*(.+)", TEXT)[0].strip().strip("\"'")
  35. REDIS_PWD = re.findall(r"password:\s*(.+)", TEXT)[1].strip().strip("\"'")
  36. S = requests.Session()
  37. S.trust_env = False
  38. passed = 0
  39. failed = 0
  40. created_orders = []
  41. points_setting_created = False
  42. test_coupon_id = None
  43. stock_backup = {}
  44. address_backup = None
  45. def check(name, cond, detail=""):
  46. global passed, failed
  47. if cond:
  48. passed += 1
  49. print(f" PASS {name}")
  50. else:
  51. failed += 1
  52. print(f" FAIL {name} {detail}")
  53. def call(method, path, params=None, body=None, token=TOKEN):
  54. headers = {"platform": "MP-WEIXIN"}
  55. if token:
  56. headers["Access-Token"] = token
  57. if method == "GET":
  58. resp = S.get(BASE + path, params=params or {}, headers=headers, timeout=60)
  59. else:
  60. resp = S.post(BASE + path, params=params or {}, json=body, headers=headers, timeout=60)
  61. try:
  62. return resp.json()
  63. except Exception:
  64. return {"status": resp.status_code, "raw": resp.text[:300]}
  65. def get(path, params=None, token=TOKEN):
  66. return call("GET", path, params=params, token=token)
  67. def post(path, body=None, params=None, token=TOKEN):
  68. return call("POST", path, params=params, body=body, token=token)
  69. def snake_keys(obj, prefix="$"):
  70. found = []
  71. if isinstance(obj, dict):
  72. for k, v in obj.items():
  73. if isinstance(k, str) and re.fullmatch(r"[a-z0-9]+(_[a-z0-9]+)+", k):
  74. found.append(f"{prefix}.{k}")
  75. found.extend(snake_keys(v, f"{prefix}.{k}"))
  76. elif isinstance(obj, list):
  77. for i, v in enumerate(obj[:3]):
  78. found.extend(snake_keys(v, f"{prefix}[{i}]"))
  79. return found
  80. def db(sql):
  81. conn = pymysql.connect(host="192.168.1.177", port=3306, user="root", password=DB_PWD,
  82. database="shop", charset="utf8mb4", autocommit=True)
  83. try:
  84. cur = conn.cursor()
  85. cur.execute(sql)
  86. return cur.fetchall()
  87. finally:
  88. conn.close()
  89. def db_exec(sql):
  90. conn = pymysql.connect(host="192.168.1.177", port=3306, user="root", password=DB_PWD,
  91. database="shop", charset="utf8mb4", autocommit=True)
  92. try:
  93. cur = conn.cursor()
  94. cur.execute(sql)
  95. return cur.lastrowid
  96. finally:
  97. conn.close()
  98. def redis_set(key, value):
  99. sock = socket.create_connection(("192.168.1.177", 6379), timeout=5)
  100. try:
  101. cmd(sock, "AUTH", REDIS_PWD)
  102. cmd(sock, "SET", key, str(value))
  103. cmd(sock, "EXPIRE", key, "3600")
  104. finally:
  105. sock.close()
  106. def field(obj, name, default=None):
  107. return obj.get(name, default) if isinstance(obj, dict) else default
  108. def make_order(order_status, pay_status, delivery_status, receipt_status, is_comment=0, with_goods=True):
  109. """造一条测试订单,返回 order_id(订单商品取测试商品)"""
  110. order_no = "TEST" + str(os.getpid()) + str(len(created_orders))
  111. order_id = db_exec(
  112. "INSERT INTO yoshop_order (order_no, order_type, total_price, order_price, coupon_id, coupon_money, "
  113. "points_money, points_num, pay_price, update_price, buyer_remark, pay_method, pay_status, trade_id, "
  114. "delivery_type, express_price, delivery_status, receipt_status, order_status, points_bonus, "
  115. "is_settled, is_comment, order_source, order_source_id, order_source_data, platform, user_id, "
  116. "is_delete, store_id, create_time, update_time) VALUES "
  117. f"('{order_no}', 10, 80.00, 80.00, 0, 0.00, 0.00, 0, 83.00, 0.00, '', '', {pay_status}, 0, "
  118. f"10, 3.00, {delivery_status}, {receipt_status}, {order_status}, 0, 0, {is_comment}, 10, 0, '[]', "
  119. f"'H5', {USER_ID}, 0, 10001, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
  120. created_orders.append(order_id)
  121. if with_goods:
  122. db_exec(
  123. "INSERT INTO yoshop_order_goods (goods_id, goods_type, goods_name, image_id, deduct_stock_type, "
  124. "spec_type, goods_sku_id, goods_props, content, goods_no, goods_price, line_price, goods_weight, "
  125. "is_user_grade, grade_ratio, grade_goods_price, grade_total_money, coupon_money, points_money, "
  126. "points_num, points_bonus, total_num, total_price, total_pay_price, delivery_status, delivery_num, "
  127. "is_comment, order_id, user_id, goods_source_id, store_id, create_time) VALUES "
  128. f"({GOODS_ID}, 10, '正宗荔浦芋头', 0, 10, 20, '{SKU_ID}', '', '', '', 80.00, 100.00, 4.5, "
  129. f"0, 0, 0.00, 0.00, 0.00, 0.00, 0, 0, 1, 80.00, 80.00, 10, 0, 0, {order_id}, {USER_ID}, 0, "
  130. "10001, UNIX_TIMESTAMP())")
  131. return order_id
  132. def cleanup():
  133. try:
  134. ids = ",".join(str(i) for i in created_orders) or "0"
  135. db_exec(f"DELETE FROM yoshop_comment_image WHERE comment_id IN "
  136. f"(SELECT comment_id FROM yoshop_comment WHERE order_id IN ({ids}))")
  137. db_exec(f"DELETE FROM yoshop_comment WHERE order_id IN ({ids})")
  138. db_exec(f"DELETE FROM yoshop_order_goods WHERE order_id IN ({ids})")
  139. db_exec(f"DELETE FROM yoshop_order_address WHERE order_id IN ({ids})")
  140. db_exec(f"DELETE FROM yoshop_order WHERE order_id IN ({ids})")
  141. if stock_backup:
  142. db_exec(f"UPDATE yoshop_goods SET stock_total={stock_backup['stock_total']} WHERE goods_id={GOODS_ID}")
  143. db_exec(f"UPDATE yoshop_goods_sku SET stock_num={stock_backup['stock_num']} "
  144. f"WHERE goods_id={GOODS_ID} AND goods_sku_id='{SKU_ID}'")
  145. if address_backup is not None:
  146. db_exec(f"UPDATE yoshop_user SET address_id={address_backup} WHERE user_id={USER_ID}")
  147. db_exec(f"UPDATE yoshop_cart SET is_delete=1 WHERE user_id={USER_ID} AND goods_id={GOODS_ID}")
  148. if points_setting_created:
  149. db_exec("DELETE FROM yoshop_store_setting WHERE store_id=10001 AND `key`='points'")
  150. if test_coupon_id:
  151. db_exec("DELETE FROM yoshop_user_coupon WHERE name='接口自测券'")
  152. db_exec(f"UPDATE yoshop_user SET points=0 WHERE user_id={USER_ID}")
  153. db_exec(f"DELETE FROM yoshop_user_points_log WHERE user_id={USER_ID} AND `describe` LIKE '%接口自测%'")
  154. print(" CLEAN 测试数据已清理")
  155. except Exception as e: # noqa: BLE001
  156. print(" CLEAN 失败:", e)
  157. def main():
  158. global address_backup, points_setting_created, test_coupon_id
  159. print("== 客户端 api 第 2 批(购物车 / 结算下单 / 订单 / 评价 / 地址) ==")
  160. redis_set("yoshop:api:token:" + TOKEN, USER_ID)
  161. rows = db(f"SELECT address_id FROM yoshop_user WHERE user_id={USER_ID}")
  162. address_backup = rows[0][0] if rows else 0
  163. stock = db(f"SELECT stock_total FROM yoshop_goods WHERE goods_id={GOODS_ID}")[0][0]
  164. sku_stock = db(f"SELECT stock_num FROM yoshop_goods_sku WHERE goods_id={GOODS_ID} "
  165. f"AND goods_sku_id='{SKU_ID}'")[0][0]
  166. stock_backup.update(stock_total=stock, stock_num=sku_stock)
  167. db_exec(f"UPDATE yoshop_cart SET is_delete=1 WHERE user_id={USER_ID}")
  168. # ---------------------------------------------------------------- 未登录
  169. r = get("/api/cart/list", token=None)
  170. check("未登录购物车返回 401", r.get("status") == 401, r.get("message"))
  171. # ---------------------------------------------------------------- 收货地址
  172. print("-- 收货地址")
  173. r = get("/api/address/list")
  174. check("address/list status=200", r.get("status") == 200, r.get("message"))
  175. addr_list = field(r.get("data"), "list") or []
  176. check("address/list 是数组且含默认地址", any(a.get("addressId") == ADDRESS_ID for a in addr_list), addr_list)
  177. check("地址节点无 snake_case", not snake_keys(addr_list), snake_keys(addr_list)[:3])
  178. r = post("/api/address/add", {
  179. "name": "接口自测", "phone": "13800000000", "detail": "自测详细地址",
  180. "region": [{"value": 110000}, {"value": 110100}, {"value": 110101}],
  181. })
  182. check("address/add 添加成功", r.get("status") == 200 and r.get("message") == "添加成功", r)
  183. new_id = db(f"SELECT MAX(address_id) FROM yoshop_user_address WHERE user_id={USER_ID}")[0][0]
  184. row = db(f"SELECT name, province_id, city_id, region_id FROM yoshop_user_address WHERE address_id={new_id}")[0]
  185. check("address/add 省市区解析正确", tuple(row[1:]) == (110000, 110100, 110101), row)
  186. r = get("/api/address/default_id")
  187. check("address/default_id 原默认地址不变", field(r.get("data"), "defaultId") == ADDRESS_ID,
  188. r.get("data"))
  189. r = get("/api/address/detail", {"addressId": new_id})
  190. check("address/detail 返回新地址", field(field(r.get("data"), "detail"), "name") == "接口自测", r.get("data"))
  191. r = post("/api/address/edit", {
  192. "addressId": new_id, "name": "接口自测2", "phone": "13900000000", "detail": "改后地址",
  193. "region": [{"value": 310000}, {"value": 310100}, {"value": 310104}],
  194. })
  195. check("address/edit 更新成功", r.get("status") == 200 and r.get("message") == "更新成功", r)
  196. row = db(f"SELECT name, province_id, city_id FROM yoshop_user_address WHERE address_id={new_id}")[0]
  197. check("address/edit 已落库", row == ("接口自测2", 310000, 310100), row)
  198. r = post("/api/address/set_default", {"addressId": new_id})
  199. check("address/set_default 设置成功", r.get("status") == 200, r)
  200. check("address/set_default 落库", db(f"SELECT address_id FROM yoshop_user WHERE user_id={USER_ID}")[0][0] == new_id)
  201. r = post("/api/address/add", {"name": "缺省市区"})
  202. check("address/add 缺省市区报错", r.get("status") == 500, r)
  203. r = post("/api/address/remove", {"addressId": new_id})
  204. check("address/remove 删除成功", r.get("status") == 200 and r.get("message") == "删除成功", r)
  205. check("address/remove 清空默认地址", db(f"SELECT address_id FROM yoshop_user WHERE user_id={USER_ID}")[0][0] == 0)
  206. db_exec(f"UPDATE yoshop_user SET address_id={ADDRESS_ID} WHERE user_id={USER_ID}")
  207. r = get("/api/address/detail", {"addressId": new_id})
  208. check("address/detail 已删除不可见", r.get("status") == 500, r)
  209. # ---------------------------------------------------------------- 购物车
  210. print("-- 购物车")
  211. r = get("/api/cart/list")
  212. check("cart/list status=200", r.get("status") == 200, r.get("message"))
  213. check("cart/list 初始 cartTotal=0", field(r.get("data"), "cartTotal") == 0, r.get("data"))
  214. r = post("/api/cart/add", {"goodsId": GOODS_ID, "goodsSkuId": SKU_ID, "goodsNum": 1})
  215. check("cart/add 加入购物车", r.get("status") == 200 and field(r.get("data"), "cartTotal") == 1, r)
  216. r = post("/api/cart/add", {"goodsId": GOODS_ID, "goodsSkuId": SKU_ID, "goodsNum": 1})
  217. check("cart/add 同规格累加数量", field(r.get("data"), "cartTotal") == 2, r.get("data"))
  218. r = get("/api/cart/list")
  219. cart_list = field(r.get("data"), "list") or []
  220. check("cart/list 一条记录", len(cart_list) == 1, len(cart_list))
  221. if cart_list:
  222. item = cart_list[0]
  223. goods = field(item, "goods") or {}
  224. check("cart/list 含 goods.skuInfo", field(goods, "skuInfo") is not None, list(goods))
  225. check("cart/list skuInfo.goodsPrice 正确",
  226. str(field(field(goods, "skuInfo"), "goodsPrice")) == SKU_PRICE, field(goods, "skuInfo"))
  227. check("cart/list 记录隐藏 storeId/createTime",
  228. not ({"storeId", "createTime", "isDelete"} & set(item)), list(item))
  229. check("cart/list 无 snake_case 键", not snake_keys(cart_list), snake_keys(cart_list)[:3])
  230. r = post("/api/cart/update", {"goodsId": GOODS_ID, "goodsSkuId": SKU_ID, "goodsNum": 3})
  231. check("cart/update 更新数量", field(r.get("data"), "cartTotal") == 3, r.get("data"))
  232. check("cart/update 落库", db(f"SELECT goods_num FROM yoshop_cart WHERE user_id={USER_ID} "
  233. f"AND is_delete=0 AND goods_id={GOODS_ID}")[0][0] == 3)
  234. r = get("/api/cart/total")
  235. check("cart/total 返回 3", field(r.get("data"), "cartTotal") == 3, r.get("data"))
  236. r = post("/api/cart/add", {"goodsId": 999999, "goodsSkuId": "0", "goodsNum": 1})
  237. check("cart/add 商品不存在报错", r.get("status") == 500, r)
  238. r = post("/api/cart/add", {"goodsId": GOODS_ID, "goodsSkuId": SKU_ID, "goodsNum": 99999})
  239. check("cart/add 超库存报错", r.get("status") == 500 and "库存" in (r.get("message") or ""), r)
  240. # ---------------------------------------------------------------- 结算台
  241. print("-- 结算台")
  242. r = get("/api/checkout/order", {"mode": "buyNow", "goodsId": FREE_GOODS_ID,
  243. "goodsSkuId": FREE_SKU_ID, "goodsNum": 2})
  244. order = field(r.get("data"), "order") or {}
  245. check("checkout 包邮商品订单总额", order.get("orderTotalPrice") == "79.80", order.get("orderTotalPrice"))
  246. check("checkout 包邮运费 0.00", order.get("expressPrice") == "0.00", order.get("expressPrice"))
  247. check("checkout 含 personal/setting", {"personal", "setting"} <= set(r.get("data") or {}), list(r.get("data") or {}))
  248. check("checkout personal 四项",
  249. set(field(r.get("data"), "personal") or {}) == {"userId", "balance", "points", "addressId"},
  250. field(r.get("data"), "personal"))
  251. check("checkout setting 三项",
  252. set(field(r.get("data"), "setting") or {}) == {"deliveryType", "pointsName", "pointsDescribe"},
  253. field(r.get("data"), "setting"))
  254. check("checkout 有地址且 isIntraRegion", order.get("existAddress") is True and order.get("isIntraRegion") is True,
  255. order.get("existAddress"))
  256. r = get("/api/checkout/order", {"mode": "buyNow", "goodsId": GOODS_ID,
  257. "goodsSkuId": SKU_ID, "goodsNum": 1})
  258. order = field(r.get("data"), "order") or {}
  259. check("checkout 首件运费 3.00", order.get("expressPrice") == "3.00", order.get("expressPrice"))
  260. check("checkout 订单实付 83.00", order.get("orderPayPrice") == "83.00", order.get("orderPayPrice"))
  261. check("checkout 商品行字段完整", {"goodsPrice", "totalNum", "totalPrice", "totalPayPrice", "skuInfo"}
  262. <= set((order.get("goodsList") or [{}])[0]), list((order.get("goodsList") or [{}])[0]))
  263. r = get("/api/checkout/order", {"mode": "buyNow", "goodsId": GOODS_ID,
  264. "goodsSkuId": SKU_ID, "goodsNum": 2})
  265. order = field(r.get("data"), "order") or {}
  266. check("checkout 续件运费 6.00", order.get("expressPrice") == "6.00", order.get("expressPrice"))
  267. check("checkout 续件实付 166.00", order.get("orderPayPrice") == "166.00", order.get("orderPayPrice"))
  268. check("checkout 无 snake_case 键", not snake_keys(r.get("data")), snake_keys(r.get("data"))[:3])
  269. r = get("/api/checkout/order", {"mode": "cart", "cartIds": ",".join(
  270. str(x[0]) for x in db(f"SELECT id FROM yoshop_cart WHERE user_id={USER_ID} AND is_delete=0"))})
  271. order = field(r.get("data"), "order") or {}
  272. check("checkout 购物车模式商品数 3", order.get("orderTotalNum") == 3, order.get("orderTotalNum"))
  273. check("checkout 购物车模式总额 240.00", order.get("orderTotalPrice") == "240.00", order.get("orderTotalPrice"))
  274. r = get("/api/checkout/order", {"mode": "buyNow", "goodsId": GOODS_ID,
  275. "goodsSkuId": SKU_ID, "goodsNum": 99999})
  276. order = field(r.get("data"), "order") or {}
  277. check("checkout 超库存 hasError", order.get("hasError") is True and "库存" in (order.get("errorMsg") or ""),
  278. order.get("errorMsg"))
  279. r = get("/api/checkout/order", {"mode": "unknown"})
  280. check("checkout 非法模式报错", r.get("status") == 500, r.get("message"))
  281. # ---------------------------------------------------------------- 下单
  282. print("-- 下单")
  283. before = db(f"SELECT stock_total FROM yoshop_goods WHERE goods_id={GOODS_ID}")[0][0]
  284. r = post("/api/checkout/submit", {"mode": "buyNow", "goodsId": GOODS_ID,
  285. "goodsSkuId": SKU_ID, "goodsNum": 1, "remark": "接口自测"})
  286. order_id = field(r.get("data"), "orderId")
  287. check("checkout/submit 下单成功", r.get("status") == 200 and order_id, r)
  288. if not order_id:
  289. print(f"\n结果:PASS {passed} / FAIL {failed}(下单失败,后续订单断言跳过)")
  290. return 1
  291. created_orders.append(order_id)
  292. row = db(f"SELECT order_no, user_id, order_status, pay_status, delivery_type, express_price, "
  293. f"pay_price, buyer_remark, is_delete FROM yoshop_order WHERE order_id={order_id}")[0]
  294. check("订单已落库且待支付", row[2] == 10 and row[3] == 10, row)
  295. check("订单运费 3.00 / 实付 83.00", str(row[5]) == "3.00" and str(row[6]) == "83.00", row)
  296. check("订单备注已保存", row[7] == "接口自测", row[7])
  297. goods_row = db(f"SELECT goods_id, goods_sku_id, goods_price, total_num, total_price, total_pay_price "
  298. f"FROM yoshop_order_goods WHERE order_id={order_id}")[0]
  299. check("订单商品已落库", goods_row[0] == GOODS_ID and str(goods_row[2]) == SKU_PRICE and goods_row[3] == 1, goods_row)
  300. check("订单收货地址已落库", db(f"SELECT COUNT(*) FROM yoshop_order_address WHERE order_id={order_id}")[0][0] == 1)
  301. after = db(f"SELECT stock_total FROM yoshop_goods WHERE goods_id={GOODS_ID}")[0][0]
  302. check("下单扣减商品库存", after == before - 1, (before, after))
  303. # ---------------------------------------------------------------- 订单接口
  304. print("-- 订单")
  305. r = get("/api/order/detail", {"orderId": order_id})
  306. detail = field(r.get("data"), "order") or {}
  307. check("order/detail status=200", r.get("status") == 200, r.get("message"))
  308. check("order/detail 含 goods/address/stateText", {"goods", "address", "stateText"} <= set(detail), list(detail))
  309. check("order/detail 隐藏 merchantRemark/tradeId/isDelete",
  310. not ({"merchantRemark", "tradeId", "isDelete", "storeId"} & set(detail)), list(detail))
  311. check("order/detail 商品隐藏 image/content",
  312. not ({"image", "content", "storeId"} & set((detail.get("goods") or [{}])[0])),
  313. list((detail.get("goods") or [{}])[0]))
  314. check("order/detail setting.pointsName 键存在", "pointsName" in (field(r.get("data"), "setting") or {}))
  315. check("order/detail 无 snake_case 键", not snake_keys(r.get("data")), snake_keys(r.get("data"))[:3])
  316. r = get("/api/order/list", {"dataType": "payment"})
  317. page = field(r.get("data"), "list") or {}
  318. ids = [o.get("orderId") for o in (page.get("data") or [])]
  319. check("order/list 待付款含新订单", order_id in ids, ids)
  320. check("order/list 分页字段 perPage=15", page.get("perPage") == 15, page.get("perPage"))
  321. r = get("/api/order/todo_counts")
  322. counts = field(r.get("data"), "counts") or {}
  323. check("order/todo_counts 四个键", set(counts) == {"payment", "delivery", "received", "refund"}, counts)
  324. check("order/todo_counts 待付款 >=1", counts.get("payment", 0) >= 1, counts)
  325. r = get("/api/order/express", {"orderId": order_id})
  326. check("order/express 未发货返回空数组", r.get("status") == 200 and r.get("data") == {"express": []}, r.get("data"))
  327. r = post("/api/order/cancel", {"orderId": order_id})
  328. check("order/cancel 未支付订单取消成功",
  329. r.get("status") == 200 and r.get("message") == "订单已取消成功", r)
  330. check("order/cancel 订单状态 20",
  331. db(f"SELECT order_status FROM yoshop_order WHERE order_id={order_id}")[0][0] == 20)
  332. check("order/cancel 回滚库存",
  333. db(f"SELECT stock_total FROM yoshop_goods WHERE goods_id={GOODS_ID}")[0][0] == before)
  334. # 收货:造已发货订单
  335. receipt_order = make_order(10, 20, 20, 10)
  336. r = post("/api/order/receipt", {"orderId": receipt_order})
  337. check("order/receipt 确认收货成功", r.get("status") == 200 and r.get("message") == "确认收货成功", r)
  338. row = db(f"SELECT order_status, receipt_status FROM yoshop_order WHERE order_id={receipt_order}")[0]
  339. check("order/receipt 状态已更新", row == (30, 20), row)
  340. r = post("/api/order/receipt", {"orderId": receipt_order})
  341. check("order/receipt 重复收货报错", r.get("status") == 500 and "不合法" in (r.get("message") or ""), r)
  342. r = get("/api/order/detail", {"orderId": 99999999})
  343. check("order/detail 不存在报错", r.get("status") == 500 and "订单不存在" in (r.get("message") or ""), r)
  344. # ---------------------------------------------------------------- 订单评价
  345. print("-- 订单评价")
  346. comment_order = make_order(30, 20, 20, 20)
  347. r = get("/api/order.comment/list", {"orderId": comment_order})
  348. goods_list = field(r.get("data"), "goodsList") or []
  349. check("order.comment/list 待评价商品 1 条", len(goods_list) == 1, len(goods_list))
  350. order_goods_id = field(goods_list[0], "orderGoodsId") if goods_list else None
  351. check("order.comment/list 商品含 goodsImage", "goodsImage" in (goods_list[0] if goods_list else {}))
  352. r = post("/api/order.comment/submit", [
  353. {"orderGoodsId": order_goods_id, "goodsId": GOODS_ID, "score": 10,
  354. "content": "接口自测评价", "uploaded": []},
  355. ], params={"orderId": comment_order})
  356. check("order.comment/submit 评价成功", r.get("status") == 200 and r.get("message") == "评价发表成功", r)
  357. check("评价已落库", db(f"SELECT content FROM yoshop_comment WHERE order_id={comment_order}")[0][0] == "接口自测评价")
  358. check("订单商品标记已评价",
  359. db(f"SELECT is_comment FROM yoshop_order_goods WHERE order_id={comment_order}")[0][0] == 1)
  360. check("订单标记已评价", db(f"SELECT is_comment FROM yoshop_order WHERE order_id={comment_order}")[0][0] == 1)
  361. r = get("/api/order.comment/list", {"orderId": comment_order})
  362. check("已评价后列表报错", r.get("status") == 500 and "已完成评价" in (r.get("message") or ""), r)
  363. # 未完成订单不可评价
  364. pending_order = make_order(10, 10, 10, 10)
  365. r = get("/api/order.comment/list", {"orderId": pending_order})
  366. check("未完成订单不可评价", r.get("status") == 500 and "未完成" in (r.get("message") or ""), r)
  367. # ---------------------------------------------------------------- 优惠券 + 积分抵扣
  368. print("-- 优惠券与积分抵扣(临时造数据)")
  369. points_setting = json.dumps({
  370. "pointsName": "积分", "describe": "接口自测",
  371. "isShoppingDiscount": 1, "isShoppingGift": 1, "giftRatio": 100,
  372. "discount": {"fullOrderPrice": 0, "maxMoneyRatio": 100, "discountRatio": 0.01},
  373. }, ensure_ascii=False)
  374. db_exec("INSERT INTO yoshop_store_setting (`key`, `describe`, `values`, store_id, create_time, update_time) "
  375. f"VALUES ('points', '积分设置', '{points_setting}', 10001, 0, UNIX_TIMESTAMP())")
  376. points_setting_created = True
  377. db_exec(f"UPDATE yoshop_user SET points=1000 WHERE user_id={USER_ID}")
  378. coupon_row = db_exec(
  379. "INSERT INTO yoshop_user_coupon (coupon_id, name, coupon_type, reduce_price, discount, min_price, "
  380. "expire_type, expire_day, start_time, end_time, apply_range, apply_range_config, `describe`, "
  381. "is_expire, is_use, user_id, store_id, create_time, update_time) VALUES "
  382. "(0, '接口自测券', 10, 5.00, 0, 0.00, 10, 30, UNIX_TIMESTAMP()-3600, UNIX_TIMESTAMP()+86400, "
  383. f"10, '{{\"goodsIds\":[]}}', '', 0, 0, {USER_ID}, 10001, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
  384. test_coupon_id = db(f"SELECT user_coupon_id FROM yoshop_user_coupon WHERE name='接口自测券'")[0][0]
  385. r = get("/api/checkout/order", {"mode": "buyNow", "goodsId": GOODS_ID, "goodsSkuId": SKU_ID,
  386. "goodsNum": 1, "couponId": test_coupon_id, "isUsePoints": 1})
  387. order = field(r.get("data"), "order") or {}
  388. check("结算台返回可用优惠券", len(order.get("couponList") or []) >= 1, order.get("couponList"))
  389. check("优惠券抵扣 5.00", order.get("couponMoney") == "5.00", order.get("couponMoney"))
  390. check("积分可抵扣 isAllowPoints", order.get("isAllowPoints") is True, order.get("isAllowPoints"))
  391. check("积分抵扣数量 1000", order.get("pointsNum") == 1000, order.get("pointsNum"))
  392. check("积分抵扣金额 10.00", order.get("pointsMoney") == "10.00", order.get("pointsMoney"))
  393. check("订单金额 65.00 / 实付 68.00",
  394. order.get("orderPrice") == "65.00" and order.get("orderPayPrice") == "68.00",
  395. (order.get("orderPrice"), order.get("orderPayPrice")))
  396. check("赠送积分 65", order.get("pointsBonus") == 65, order.get("pointsBonus"))
  397. line = (order.get("goodsList") or [{}])[0]
  398. check("商品行优惠券/积分/实付",
  399. line.get("couponMoney") == "5.00" and line.get("pointsMoney") == "10.00"
  400. and line.get("totalPayPrice") == "65.00",
  401. (line.get("couponMoney"), line.get("pointsMoney"), line.get("totalPayPrice")))
  402. r = post("/api/checkout/submit", {"mode": "buyNow", "goodsId": GOODS_ID, "goodsSkuId": SKU_ID,
  403. "goodsNum": 1, "couponId": test_coupon_id, "isUsePoints": 1})
  404. coupon_order = field(r.get("data"), "orderId")
  405. check("带优惠券+积分下单成功", r.get("status") == 200 and coupon_order, r)
  406. if coupon_order:
  407. created_orders.append(coupon_order)
  408. row = db(f"SELECT coupon_id, coupon_money, points_money, points_num, pay_price, points_bonus "
  409. f"FROM yoshop_order WHERE order_id={coupon_order}")[0]
  410. check("订单优惠券已落库", row[0] == test_coupon_id and str(row[1]) == "5.00", row)
  411. check("订单积分抵扣已落库", str(row[2]) == "10.00" and row[3] == 1000, row)
  412. check("订单实付 68.00 / 赠送积分 65", str(row[4]) == "68.00" and row[5] == 65, row)
  413. check("优惠券已核销",
  414. db(f"SELECT is_use FROM yoshop_user_coupon WHERE user_coupon_id={test_coupon_id}")[0][0] == 1)
  415. check("用户积分已扣减 1000→0", db(f"SELECT points FROM yoshop_user WHERE user_id={USER_ID}")[0][0] == 0)
  416. check("积分流水已记录",
  417. db(f"SELECT COUNT(*) FROM yoshop_user_points_log WHERE user_id={USER_ID} AND value=-1000")[0][0] >= 1)
  418. r = post("/api/order/cancel", {"orderId": coupon_order})
  419. check("取消订单成功", r.get("status") == 200, r)
  420. check("取消回退积分", db(f"SELECT points FROM yoshop_user WHERE user_id={USER_ID}")[0][0] == 1000)
  421. check("取消回退优惠券",
  422. db(f"SELECT is_use FROM yoshop_user_coupon WHERE user_coupon_id={test_coupon_id}")[0][0] == 0)
  423. print(f"\n结果:PASS {passed} / FAIL {failed}")
  424. return 1 if failed else 0
  425. if __name__ == "__main__":
  426. code = 1
  427. try:
  428. code = main()
  429. finally:
  430. cleanup()
  431. sys.exit(code)