巴青农资商城

test_consumer_flow.py 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # ============================================================
  2. # C端消费者 — 全量端到端测试
  3. # 覆盖 19 个 Controller,约 60 个 API 端点
  4. # ============================================================
  5. import pytest
  6. import json
  7. import random
  8. from api_client import ApiClient
  9. from config import TEST_PASSWORD, KEEP_TEST_DATA
  10. # ============================================================
  11. # 首页浏览 (HomeAppController) — 无需登录
  12. # ============================================================
  13. class TestConsumerHome:
  14. def test_45_home_banners(self, client, admin_token):
  15. """首页Banner列表"""
  16. client.clear_token()
  17. resp = client.get("/api/home/banners")
  18. client.assert_ok("查询Banner失败")
  19. banners = client.extract_data() or []
  20. print(f" [OK] Banner count={len(banners)}")
  21. def test_46_home_categories(self, client):
  22. """首页分类导航"""
  23. resp = client.get("/api/home/categories")
  24. client.assert_ok("查询首页分类失败")
  25. cats = client.extract_data() or []
  26. print(f" [OK] 首页分类 count={len(cats)}")
  27. def test_47_home_hot_goods(self, client):
  28. """热销商品"""
  29. resp = client.get("/api/home/hot-goods")
  30. client.assert_ok("查询热销商品失败")
  31. goods = client.extract_data() or []
  32. print(f" [OK] 热销商品 count={len(goods)}")
  33. if goods:
  34. client._test_hot_goods_id = goods[0].get("goodsId") or goods[0].get("id")
  35. # ============================================================
  36. # 搜索 (SearchAppController)
  37. # ============================================================
  38. class TestConsumerSearch:
  39. def test_48_search(self, client):
  40. """全站搜索"""
  41. resp = client.get("/api/search", params={"keyword": "测试"})
  42. client.assert_ok("搜索失败")
  43. result = client.extract_data() or {}
  44. print(f" [OK] 搜索正常: {json.dumps(result, ensure_ascii=False)[:100]}")
  45. def test_49_search_empty(self, client):
  46. """搜索空关键词"""
  47. resp = client.get("/api/search", params={"keyword": ""})
  48. client.assert_ok("空搜索失败")
  49. print(f" [OK] 空搜索正常")
  50. # ============================================================
  51. # 商品详情 (GoodsAppController)
  52. # ============================================================
  53. class TestConsumerGoods:
  54. def test_50_goods_detail(self, client):
  55. """商品详情"""
  56. gid = getattr(client, '_test_hot_goods_id', None)
  57. if not gid:
  58. resp = client.get("/api/home/hot-goods")
  59. data = client.assert_ok()
  60. goods = data.get("data", [])
  61. if not goods:
  62. pytest.skip("无在售商品")
  63. gid = goods[0].get("goodsId") or goods[0].get("id")
  64. resp = client.get(f"/api/goods/{gid}")
  65. client.assert_ok("查询商品详情失败")
  66. detail = client.extract_data() or {}
  67. print(f" [OK] 商品详情: {detail.get('goodsName','')[:30]}")
  68. client._test_goods_id = gid
  69. def test_51_goods_can_purchase(self, client):
  70. """商品可购校验"""
  71. gid = getattr(client, '_test_goods_id', None)
  72. if not gid:
  73. pytest.skip("无商品ID")
  74. resp = client.get(f"/api/goods/{gid}/can-purchase")
  75. client.assert_ok("查询可购校验失败")
  76. result = client.extract_data() or {}
  77. print(f" [OK] 可购校验: {result}")
  78. def test_52_goods_reviews(self, client):
  79. """商品评价列表"""
  80. gid = getattr(client, '_test_goods_id', None)
  81. if not gid:
  82. pytest.skip("无商品ID")
  83. resp = client.get(f"/api/goods/{gid}/reviews")
  84. client.assert_ok("查询评价失败")
  85. data = client.extract_data() or {}
  86. total = data.get("total", 0) if isinstance(data, dict) else 0
  87. print(f" [OK] 商品评价 total={total}")
  88. # ============================================================
  89. # 店铺 (ShopAppController)
  90. # ============================================================
  91. class TestConsumerShop:
  92. def test_53_shop_profile(self, client):
  93. """店铺主页"""
  94. # 从首页热销商品中找店铺ID
  95. resp = client.get("/api/home/hot-goods")
  96. data = client.assert_ok()
  97. goods = data.get("data", [])
  98. shop_id = None
  99. for g in goods:
  100. sid = g.get("shopId")
  101. if sid:
  102. shop_id = sid
  103. break
  104. if not shop_id:
  105. pytest.skip("无店铺数据")
  106. client._test_shop_id = shop_id
  107. resp = client.get(f"/api/shop/{shop_id}")
  108. client.assert_ok("查询店铺主页失败")
  109. profile = client.extract_data() or {}
  110. print(f" [OK] 店铺主页: {profile.get('shopName','')[:20] if isinstance(profile, dict) else 'ok'}")
  111. def test_54_shop_categories(self, client):
  112. """店铺分类"""
  113. sid = getattr(client, '_test_shop_id', None)
  114. if not sid:
  115. pytest.skip("无店铺ID")
  116. resp = client.get(f"/api/shop/{sid}/categories")
  117. client.assert_ok("查询店铺分类失败")
  118. cats = client.extract_data() or []
  119. print(f" [OK] 店铺分类 count={len(cats)}")
  120. def test_55_shop_goods(self, client):
  121. """店铺商品列表"""
  122. sid = getattr(client, '_test_shop_id', None)
  123. if not sid:
  124. pytest.skip("无店铺ID")
  125. resp = client.get(f"/api/shop/{sid}/goods")
  126. client.assert_ok("查询店铺商品失败")
  127. data = client.extract_data() or {}
  128. print(f" [OK] 店铺商品列表正常")
  129. # ============================================================
  130. # C端分类 (CategoryAppController)
  131. # ============================================================
  132. class TestConsumerCategory:
  133. def test_56_category_tree(self, client):
  134. """C端分类树"""
  135. resp = client.get("/api/category/tree")
  136. client.assert_ok("查询分类树失败")
  137. tree = client.extract_data() or []
  138. print(f" [OK] C端分类树 nodes={len(tree)}")
  139. # ============================================================
  140. # C端地区 (RegionAppController)
  141. # ============================================================
  142. class TestConsumerRegion:
  143. def test_57_region_tree(self, client):
  144. """C端地区树"""
  145. resp = client.get("/api/region/tree")
  146. client.assert_ok("查询地区树失败")
  147. tree = client.extract_data() or []
  148. print(f" [OK] C端地区树 nodes={len(tree)}")
  149. # ============================================================
  150. # 商城配置 (MallConfigAppController)
  151. # ============================================================
  152. class TestConsumerMallConfig:
  153. def test_58_mall_config(self, client):
  154. """商城配置"""
  155. resp = client.get("/api/mall/config")
  156. client.assert_ok("查询商城配置失败")
  157. config = client.extract_data() or {}
  158. print(f" [OK] 商城配置: {json.dumps(config, ensure_ascii=False)[:120]}")
  159. # ============================================================
  160. # 协议查询 (MallServiceAgreement + MerchantEntryAgreement)
  161. # ============================================================
  162. class TestConsumerAgreement:
  163. def test_59_service_agreement_status(self, client):
  164. """服务协议状态"""
  165. resp = client.get("/api/member/serviceAgreement/status")
  166. client.assert_ok("查询服务协议状态失败")
  167. status = client.extract_data() or {}
  168. print(f" [OK] 服务协议状态: {status}")
  169. def test_60_entry_agreement(self, client):
  170. """入驻协议内容"""
  171. resp = client.get("/api/merchant/entry/agreement")
  172. client.assert_ok("查询入驻协议失败")
  173. agmt = client.extract_data() or {}
  174. print(f" [OK] 入驻协议: {str(agmt)[:80]}")
  175. def test_61_entry_agreement_status(self, client):
  176. """入驻协议状态"""
  177. resp = client.get("/api/merchant/entry/status")
  178. client.assert_ok("查询入驻协议状态失败")
  179. status = client.extract_data() or {}
  180. print(f" [OK] 入驻协议状态: {status}")
  181. # ============================================================
  182. # 会员注册登录 (MemberAppController)
  183. # ============================================================
  184. class TestConsumerMember:
  185. @pytest.fixture(autouse=True)
  186. def setup_member(self, client, member_token):
  187. """复用 conftest 中的 member_token fixture"""
  188. pass
  189. def test_62_member_profile(self, client, member_token):
  190. """会员基本信息"""
  191. resp = client.get("/api/member/profile")
  192. client.assert_ok("查询会员信息失败")
  193. profile = client.extract_data() or {}
  194. print(f" [OK] 会员信息: {profile.get('nickName','')[:20]}, mobile={profile.get('mobile','')}")
  195. def test_63_member_update_profile(self, client, member_token):
  196. """会员更新信息"""
  197. resp = client.put("/api/member/profile", json={
  198. "nickName": f"E2E用户_{random.randint(100,999)}"
  199. })
  200. client.assert_ok("更新会员信息失败")
  201. print(f" [OK] 会员信息更新成功")
  202. def test_64_member_address_list(self, client, member_token):
  203. """会员地址列表"""
  204. resp = client.get("/api/member/address/list")
  205. client.assert_ok("查询地址列表失败")
  206. addrs = client.extract_data() or []
  207. print(f" [OK] 地址列表 count={len(addrs)}")
  208. def test_65_member_add_address(self, client, member_token):
  209. """新增收货地址"""
  210. resp = client.post("/api/member/address", json={
  211. "receiverName": "张三",
  212. "receiverMobile": "13800138000",
  213. "province": "北京市",
  214. "city": "北京市",
  215. "district": "朝阳区",
  216. "detailAddress": "测试街道100号",
  217. "fullAddress": "北京市北京市朝阳区测试街道100号",
  218. "isDefault": True,
  219. })
  220. data = client.assert_ok("新增地址失败")
  221. addr_id = data.get("data")
  222. client._test_address_id = addr_id
  223. print(f" [OK] 新增地址 addressId={addr_id}")
  224. def test_66_member_set_default_address(self, client, member_token):
  225. """设置默认地址"""
  226. addr_id = getattr(client, '_test_address_id', None)
  227. if not addr_id:
  228. pytest.skip("无地址ID")
  229. resp = client.put(f"/api/member/address/{addr_id}/default")
  230. client.assert_ok("设置默认地址失败")
  231. print(f" [OK] 设置默认地址 {addr_id}")
  232. def test_67_member_update_address(self, client, member_token):
  233. """更新收货地址"""
  234. addr_id = getattr(client, '_test_address_id', None)
  235. if not addr_id:
  236. pytest.skip("无地址ID")
  237. resp = client.put("/api/member/address", json={
  238. "addressId": addr_id,
  239. "receiverName": "张三(改)",
  240. "receiverMobile": "13800138000",
  241. "detailAddress": "测试街道200号",
  242. })
  243. client.assert_ok("更新地址失败")
  244. print(f" [OK] 更新地址 {addr_id}")
  245. # ============================================================
  246. # 购物车 (CartAppController) — 需登录
  247. # ============================================================
  248. class TestConsumerCart:
  249. def test_68_cart_list_empty(self, client, member_token):
  250. """购物车列表(空)"""
  251. resp = client.get("/api/cart")
  252. client.assert_ok("查询购物车失败")
  253. items = client.extract_data() or {}
  254. print(f" [OK] 购物车列表正常")
  255. def test_69_cart_add_item(self, client, member_token):
  256. """添加商品到购物车"""
  257. gid = getattr(client, '_test_goods_id', None)
  258. if not gid:
  259. resp = client.get("/api/home/hot-goods")
  260. data = client.assert_ok()
  261. goods = data.get("data", [])
  262. if not goods:
  263. pytest.skip("无商品")
  264. gid = goods[0].get("goodsId") or goods[0].get("id")
  265. resp = client.post("/api/cart/items", json={
  266. "goodsId": gid,
  267. "quantity": 1,
  268. })
  269. client.assert_ok("加购失败")
  270. print(f" [OK] 加购成功 goodsId={gid}")
  271. def test_70_cart_update_quantity(self, client, member_token):
  272. """更新购物车数量"""
  273. resp = client.get("/api/cart")
  274. data = client.assert_ok()
  275. cart_data = client.extract_data() or {}
  276. items = []
  277. if isinstance(cart_data, dict):
  278. items = cart_data.get("items", []) or cart_data.get("cartItems", [])
  279. if not items:
  280. items = cart_data.get("data", [])
  281. if not items:
  282. # 找任何列表
  283. for v in cart_data.values():
  284. if isinstance(v, list) and len(v) > 0:
  285. items = v
  286. break
  287. if not items:
  288. pytest.skip("购物车无商品")
  289. ci = items[0]
  290. ciid = ci.get("cartItemId") or ci.get("id")
  291. if not ciid:
  292. pytest.skip("购物车项无ID")
  293. resp = client.put(f"/api/cart/items/{ciid}/quantity", json={"quantity": 2})
  294. client.assert_ok("更新数量失败")
  295. print(f" [OK] 更新数量 cartItemId={ciid} qty=2")
  296. client._test_cart_item_id = ciid
  297. def test_71_cart_remove_item(self, client, member_token):
  298. """删除购物车商品"""
  299. ciid = getattr(client, '_test_cart_item_id', None)
  300. if not ciid:
  301. # 先加一个再到购物车查看
  302. gid = getattr(client, '_test_goods_id', None)
  303. if not gid:
  304. pytest.skip("无商品ID")
  305. client.post("/api/cart/items", json={"goodsId": gid, "quantity": 1})
  306. resp = client.get("/api/cart")
  307. data = client.assert_ok()
  308. cart_data = client.extract_data() or {}
  309. items = []
  310. if isinstance(cart_data, dict):
  311. for v in cart_data.values():
  312. if isinstance(v, list) and len(v) > 0:
  313. items = v
  314. break
  315. if not items:
  316. pytest.skip("购物车无商品")
  317. ciid = items[0].get("cartItemId") or items[0].get("id")
  318. resp = client.delete(f"/api/cart/items/{ciid}")
  319. client.assert_ok("删除购物车商品失败")
  320. print(f" [OK] 删除购物车商品 {ciid}")
  321. # ============================================================
  322. # 结算下单 (CheckoutAppController + OrderAppController)
  323. # ============================================================
  324. class TestConsumerCheckout:
  325. def test_72_preview_and_submit_order(self, client, member_token):
  326. """预览并提交订单"""
  327. gid = getattr(client, '_test_goods_id', None)
  328. if not gid:
  329. pytest.skip("无商品")
  330. # 先有地址
  331. resp = client.get("/api/member/address/list")
  332. data = client.assert_ok()
  333. addrs = client.extract_data() or []
  334. if not addrs:
  335. pytest.skip("无收货地址")
  336. addr_id = addrs[0].get("addressId")
  337. # 预览
  338. resp = client.post("/api/checkout/preview", json={
  339. "source": "BUY_NOW",
  340. "items": [{"goodsId": gid, "quantity": 1}],
  341. })
  342. client.assert_ok("预览订单失败")
  343. preview = client.extract_data() or {}
  344. print(f" [OK] 订单预览正常")
  345. # 提交
  346. resp = client.post("/api/checkout/submit", json={
  347. "source": "BUY_NOW",
  348. "addressId": addr_id,
  349. "payType": "WEIXIN",
  350. "items": [{"goodsId": gid, "quantity": 1}],
  351. })
  352. data = client.assert_ok("提交订单失败")
  353. order_data = data.get("data", {}) if isinstance(data.get("data"), dict) else data
  354. order_id = order_data.get("orderId")
  355. if not order_id:
  356. order_ids = order_data.get("orderIds", [])
  357. order_id = order_ids[0] if order_ids else None
  358. if not order_id:
  359. print(f" [WARN] 提交订单返回结构: {json.dumps(data, ensure_ascii=False)[:150]}")
  360. pytest.skip("未获取到orderId")
  361. client._test_order_id = order_id
  362. print(f" [OK] 订单提交成功 orderId={order_id}")
  363. # ============================================================
  364. # 订单管理 (OrderAppController)
  365. # ============================================================
  366. class TestConsumerOrder:
  367. def test_73_order_badges(self, client, member_token):
  368. """订单角标"""
  369. resp = client.get("/api/order/badges")
  370. client.assert_ok("查询订单角标失败")
  371. badges = client.extract_data() or {}
  372. print(f" [OK] 订单角标: {badges}")
  373. def test_74_order_list(self, client, member_token):
  374. """订单列表"""
  375. resp = client.get("/api/order/list")
  376. client.assert_ok("查询订单列表失败")
  377. data = client.extract_data() or {}
  378. total = data.get("total", 0) if isinstance(data, dict) else 0
  379. print(f" [OK] 订单列表 total={total}")
  380. def test_75_order_detail(self, client, member_token):
  381. """订单详情"""
  382. oid = getattr(client, '_test_order_id', None)
  383. if not oid:
  384. pytest.skip("无订单ID")
  385. resp = client.get(f"/api/order/{oid}")
  386. client.assert_ok("查询订单详情失败")
  387. detail = client.extract_data() or {}
  388. status = detail.get("orderStatus", "")
  389. print(f" [OK] 订单详情 orderId={oid} status={status}")
  390. def test_76_order_pay(self, client, member_token):
  391. """订单支付(mock模式)"""
  392. oid = getattr(client, '_test_order_id', None)
  393. if not oid:
  394. pytest.skip("无订单ID")
  395. resp = client.post(f"/api/order/{oid}/pay")
  396. data = client.assert_ok("支付失败")
  397. print(f" [OK] 支付调用成功")
  398. def test_77_order_confirm_receive(self, client, member_token):
  399. """确认收货"""
  400. oid = getattr(client, '_test_order_id', None)
  401. if not oid:
  402. pytest.skip("无订单ID")
  403. resp = client.post(f"/api/order/{oid}/confirm-receive")
  404. data = client.assert_ok("确认收货失败")
  405. print(f" [OK] 确认收货 orderId={oid}")
  406. # ============================================================
  407. # 开放统计 (MallStatsOpenController)
  408. # ============================================================
  409. class TestConsumerStats:
  410. def test_78_stats_hot_category(self, client):
  411. """热销类目统计"""
  412. resp = client.get("/api/open/stats/hotCategory")
  413. client.assert_ok("查询热销类目失败")
  414. data = client.extract_data() or {}
  415. print(f" [OK] 热销类目: {json.dumps(data, ensure_ascii=False)[:100]}")
  416. def test_79_stats_overview(self, client):
  417. """商城概览统计"""
  418. resp = client.get("/api/open/stats/overview")
  419. client.assert_ok("查询商城概览失败")
  420. data = client.extract_data() or {}
  421. print(f" [OK] 商城概览: {json.dumps(data, ensure_ascii=False)[:100]}")