| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- # ============================================================
- # C端消费者 — 全量端到端测试
- # 覆盖 19 个 Controller,约 60 个 API 端点
- # ============================================================
- import pytest
- import json
- import random
- from api_client import ApiClient
- from config import TEST_PASSWORD, KEEP_TEST_DATA
- # ============================================================
- # 首页浏览 (HomeAppController) — 无需登录
- # ============================================================
- class TestConsumerHome:
- def test_45_home_banners(self, client, admin_token):
- """首页Banner列表"""
- client.clear_token()
- resp = client.get("/api/home/banners")
- client.assert_ok("查询Banner失败")
- banners = client.extract_data() or []
- print(f" [OK] Banner count={len(banners)}")
- def test_46_home_categories(self, client):
- """首页分类导航"""
- resp = client.get("/api/home/categories")
- client.assert_ok("查询首页分类失败")
- cats = client.extract_data() or []
- print(f" [OK] 首页分类 count={len(cats)}")
- def test_47_home_hot_goods(self, client):
- """热销商品"""
- resp = client.get("/api/home/hot-goods")
- client.assert_ok("查询热销商品失败")
- goods = client.extract_data() or []
- print(f" [OK] 热销商品 count={len(goods)}")
- if goods:
- client._test_hot_goods_id = goods[0].get("goodsId") or goods[0].get("id")
- # ============================================================
- # 搜索 (SearchAppController)
- # ============================================================
- class TestConsumerSearch:
- def test_48_search(self, client):
- """全站搜索"""
- resp = client.get("/api/search", params={"keyword": "测试"})
- client.assert_ok("搜索失败")
- result = client.extract_data() or {}
- print(f" [OK] 搜索正常: {json.dumps(result, ensure_ascii=False)[:100]}")
- def test_49_search_empty(self, client):
- """搜索空关键词"""
- resp = client.get("/api/search", params={"keyword": ""})
- client.assert_ok("空搜索失败")
- print(f" [OK] 空搜索正常")
- # ============================================================
- # 商品详情 (GoodsAppController)
- # ============================================================
- class TestConsumerGoods:
- def test_50_goods_detail(self, client):
- """商品详情"""
- gid = getattr(client, '_test_hot_goods_id', None)
- if not gid:
- resp = client.get("/api/home/hot-goods")
- data = client.assert_ok()
- goods = data.get("data", [])
- if not goods:
- pytest.skip("无在售商品")
- gid = goods[0].get("goodsId") or goods[0].get("id")
- resp = client.get(f"/api/goods/{gid}")
- client.assert_ok("查询商品详情失败")
- detail = client.extract_data() or {}
- print(f" [OK] 商品详情: {detail.get('goodsName','')[:30]}")
- client._test_goods_id = gid
- def test_51_goods_can_purchase(self, client):
- """商品可购校验"""
- gid = getattr(client, '_test_goods_id', None)
- if not gid:
- pytest.skip("无商品ID")
- resp = client.get(f"/api/goods/{gid}/can-purchase")
- client.assert_ok("查询可购校验失败")
- result = client.extract_data() or {}
- print(f" [OK] 可购校验: {result}")
- def test_52_goods_reviews(self, client):
- """商品评价列表"""
- gid = getattr(client, '_test_goods_id', None)
- if not gid:
- pytest.skip("无商品ID")
- resp = client.get(f"/api/goods/{gid}/reviews")
- client.assert_ok("查询评价失败")
- data = client.extract_data() or {}
- total = data.get("total", 0) if isinstance(data, dict) else 0
- print(f" [OK] 商品评价 total={total}")
- # ============================================================
- # 店铺 (ShopAppController)
- # ============================================================
- class TestConsumerShop:
- def test_53_shop_profile(self, client):
- """店铺主页"""
- # 从首页热销商品中找店铺ID
- resp = client.get("/api/home/hot-goods")
- data = client.assert_ok()
- goods = data.get("data", [])
- shop_id = None
- for g in goods:
- sid = g.get("shopId")
- if sid:
- shop_id = sid
- break
- if not shop_id:
- pytest.skip("无店铺数据")
- client._test_shop_id = shop_id
- resp = client.get(f"/api/shop/{shop_id}")
- client.assert_ok("查询店铺主页失败")
- profile = client.extract_data() or {}
- print(f" [OK] 店铺主页: {profile.get('shopName','')[:20] if isinstance(profile, dict) else 'ok'}")
- def test_54_shop_categories(self, client):
- """店铺分类"""
- sid = getattr(client, '_test_shop_id', None)
- if not sid:
- pytest.skip("无店铺ID")
- resp = client.get(f"/api/shop/{sid}/categories")
- client.assert_ok("查询店铺分类失败")
- cats = client.extract_data() or []
- print(f" [OK] 店铺分类 count={len(cats)}")
- def test_55_shop_goods(self, client):
- """店铺商品列表"""
- sid = getattr(client, '_test_shop_id', None)
- if not sid:
- pytest.skip("无店铺ID")
- resp = client.get(f"/api/shop/{sid}/goods")
- client.assert_ok("查询店铺商品失败")
- data = client.extract_data() or {}
- print(f" [OK] 店铺商品列表正常")
- # ============================================================
- # C端分类 (CategoryAppController)
- # ============================================================
- class TestConsumerCategory:
- def test_56_category_tree(self, client):
- """C端分类树"""
- resp = client.get("/api/category/tree")
- client.assert_ok("查询分类树失败")
- tree = client.extract_data() or []
- print(f" [OK] C端分类树 nodes={len(tree)}")
- # ============================================================
- # C端地区 (RegionAppController)
- # ============================================================
- class TestConsumerRegion:
- def test_57_region_tree(self, client):
- """C端地区树"""
- resp = client.get("/api/region/tree")
- client.assert_ok("查询地区树失败")
- tree = client.extract_data() or []
- print(f" [OK] C端地区树 nodes={len(tree)}")
- # ============================================================
- # 商城配置 (MallConfigAppController)
- # ============================================================
- class TestConsumerMallConfig:
- def test_58_mall_config(self, client):
- """商城配置"""
- resp = client.get("/api/mall/config")
- client.assert_ok("查询商城配置失败")
- config = client.extract_data() or {}
- print(f" [OK] 商城配置: {json.dumps(config, ensure_ascii=False)[:120]}")
- # ============================================================
- # 协议查询 (MallServiceAgreement + MerchantEntryAgreement)
- # ============================================================
- class TestConsumerAgreement:
- def test_59_service_agreement_status(self, client):
- """服务协议状态"""
- resp = client.get("/api/member/serviceAgreement/status")
- client.assert_ok("查询服务协议状态失败")
- status = client.extract_data() or {}
- print(f" [OK] 服务协议状态: {status}")
- def test_60_entry_agreement(self, client):
- """入驻协议内容"""
- resp = client.get("/api/merchant/entry/agreement")
- client.assert_ok("查询入驻协议失败")
- agmt = client.extract_data() or {}
- print(f" [OK] 入驻协议: {str(agmt)[:80]}")
- def test_61_entry_agreement_status(self, client):
- """入驻协议状态"""
- resp = client.get("/api/merchant/entry/status")
- client.assert_ok("查询入驻协议状态失败")
- status = client.extract_data() or {}
- print(f" [OK] 入驻协议状态: {status}")
- # ============================================================
- # 会员注册登录 (MemberAppController)
- # ============================================================
- class TestConsumerMember:
- @pytest.fixture(autouse=True)
- def setup_member(self, client, member_token):
- """复用 conftest 中的 member_token fixture"""
- pass
- def test_62_member_profile(self, client, member_token):
- """会员基本信息"""
- resp = client.get("/api/member/profile")
- client.assert_ok("查询会员信息失败")
- profile = client.extract_data() or {}
- print(f" [OK] 会员信息: {profile.get('nickName','')[:20]}, mobile={profile.get('mobile','')}")
- def test_63_member_update_profile(self, client, member_token):
- """会员更新信息"""
- resp = client.put("/api/member/profile", json={
- "nickName": f"E2E用户_{random.randint(100,999)}"
- })
- client.assert_ok("更新会员信息失败")
- print(f" [OK] 会员信息更新成功")
- def test_64_member_address_list(self, client, member_token):
- """会员地址列表"""
- resp = client.get("/api/member/address/list")
- client.assert_ok("查询地址列表失败")
- addrs = client.extract_data() or []
- print(f" [OK] 地址列表 count={len(addrs)}")
- def test_65_member_add_address(self, client, member_token):
- """新增收货地址"""
- resp = client.post("/api/member/address", json={
- "receiverName": "张三",
- "receiverMobile": "13800138000",
- "province": "北京市",
- "city": "北京市",
- "district": "朝阳区",
- "detailAddress": "测试街道100号",
- "fullAddress": "北京市北京市朝阳区测试街道100号",
- "isDefault": True,
- })
- data = client.assert_ok("新增地址失败")
- addr_id = data.get("data")
- client._test_address_id = addr_id
- print(f" [OK] 新增地址 addressId={addr_id}")
- def test_66_member_set_default_address(self, client, member_token):
- """设置默认地址"""
- addr_id = getattr(client, '_test_address_id', None)
- if not addr_id:
- pytest.skip("无地址ID")
- resp = client.put(f"/api/member/address/{addr_id}/default")
- client.assert_ok("设置默认地址失败")
- print(f" [OK] 设置默认地址 {addr_id}")
- def test_67_member_update_address(self, client, member_token):
- """更新收货地址"""
- addr_id = getattr(client, '_test_address_id', None)
- if not addr_id:
- pytest.skip("无地址ID")
- resp = client.put("/api/member/address", json={
- "addressId": addr_id,
- "receiverName": "张三(改)",
- "receiverMobile": "13800138000",
- "detailAddress": "测试街道200号",
- })
- client.assert_ok("更新地址失败")
- print(f" [OK] 更新地址 {addr_id}")
- # ============================================================
- # 购物车 (CartAppController) — 需登录
- # ============================================================
- class TestConsumerCart:
- def test_68_cart_list_empty(self, client, member_token):
- """购物车列表(空)"""
- resp = client.get("/api/cart")
- client.assert_ok("查询购物车失败")
- items = client.extract_data() or {}
- print(f" [OK] 购物车列表正常")
- def test_69_cart_add_item(self, client, member_token):
- """添加商品到购物车"""
- gid = getattr(client, '_test_goods_id', None)
- if not gid:
- resp = client.get("/api/home/hot-goods")
- data = client.assert_ok()
- goods = data.get("data", [])
- if not goods:
- pytest.skip("无商品")
- gid = goods[0].get("goodsId") or goods[0].get("id")
- resp = client.post("/api/cart/items", json={
- "goodsId": gid,
- "quantity": 1,
- })
- client.assert_ok("加购失败")
- print(f" [OK] 加购成功 goodsId={gid}")
- def test_70_cart_update_quantity(self, client, member_token):
- """更新购物车数量"""
- resp = client.get("/api/cart")
- data = client.assert_ok()
- cart_data = client.extract_data() or {}
- items = []
- if isinstance(cart_data, dict):
- items = cart_data.get("items", []) or cart_data.get("cartItems", [])
- if not items:
- items = cart_data.get("data", [])
- if not items:
- # 找任何列表
- for v in cart_data.values():
- if isinstance(v, list) and len(v) > 0:
- items = v
- break
- if not items:
- pytest.skip("购物车无商品")
- ci = items[0]
- ciid = ci.get("cartItemId") or ci.get("id")
- if not ciid:
- pytest.skip("购物车项无ID")
- resp = client.put(f"/api/cart/items/{ciid}/quantity", json={"quantity": 2})
- client.assert_ok("更新数量失败")
- print(f" [OK] 更新数量 cartItemId={ciid} qty=2")
- client._test_cart_item_id = ciid
- def test_71_cart_remove_item(self, client, member_token):
- """删除购物车商品"""
- ciid = getattr(client, '_test_cart_item_id', None)
- if not ciid:
- # 先加一个再到购物车查看
- gid = getattr(client, '_test_goods_id', None)
- if not gid:
- pytest.skip("无商品ID")
- client.post("/api/cart/items", json={"goodsId": gid, "quantity": 1})
- resp = client.get("/api/cart")
- data = client.assert_ok()
- cart_data = client.extract_data() or {}
- items = []
- if isinstance(cart_data, dict):
- for v in cart_data.values():
- if isinstance(v, list) and len(v) > 0:
- items = v
- break
- if not items:
- pytest.skip("购物车无商品")
- ciid = items[0].get("cartItemId") or items[0].get("id")
- resp = client.delete(f"/api/cart/items/{ciid}")
- client.assert_ok("删除购物车商品失败")
- print(f" [OK] 删除购物车商品 {ciid}")
- # ============================================================
- # 结算下单 (CheckoutAppController + OrderAppController)
- # ============================================================
- class TestConsumerCheckout:
- def test_72_preview_and_submit_order(self, client, member_token):
- """预览并提交订单"""
- gid = getattr(client, '_test_goods_id', None)
- if not gid:
- pytest.skip("无商品")
- # 先有地址
- resp = client.get("/api/member/address/list")
- data = client.assert_ok()
- addrs = client.extract_data() or []
- if not addrs:
- pytest.skip("无收货地址")
- addr_id = addrs[0].get("addressId")
- # 预览
- resp = client.post("/api/checkout/preview", json={
- "source": "BUY_NOW",
- "items": [{"goodsId": gid, "quantity": 1}],
- })
- client.assert_ok("预览订单失败")
- preview = client.extract_data() or {}
- print(f" [OK] 订单预览正常")
- # 提交
- resp = client.post("/api/checkout/submit", json={
- "source": "BUY_NOW",
- "addressId": addr_id,
- "payType": "WEIXIN",
- "items": [{"goodsId": gid, "quantity": 1}],
- })
- data = client.assert_ok("提交订单失败")
- order_data = data.get("data", {}) if isinstance(data.get("data"), dict) else data
- order_id = order_data.get("orderId")
- if not order_id:
- order_ids = order_data.get("orderIds", [])
- order_id = order_ids[0] if order_ids else None
- if not order_id:
- print(f" [WARN] 提交订单返回结构: {json.dumps(data, ensure_ascii=False)[:150]}")
- pytest.skip("未获取到orderId")
- client._test_order_id = order_id
- print(f" [OK] 订单提交成功 orderId={order_id}")
- # ============================================================
- # 订单管理 (OrderAppController)
- # ============================================================
- class TestConsumerOrder:
- def test_73_order_badges(self, client, member_token):
- """订单角标"""
- resp = client.get("/api/order/badges")
- client.assert_ok("查询订单角标失败")
- badges = client.extract_data() or {}
- print(f" [OK] 订单角标: {badges}")
- def test_74_order_list(self, client, member_token):
- """订单列表"""
- resp = client.get("/api/order/list")
- client.assert_ok("查询订单列表失败")
- data = client.extract_data() or {}
- total = data.get("total", 0) if isinstance(data, dict) else 0
- print(f" [OK] 订单列表 total={total}")
- def test_75_order_detail(self, client, member_token):
- """订单详情"""
- oid = getattr(client, '_test_order_id', None)
- if not oid:
- pytest.skip("无订单ID")
- resp = client.get(f"/api/order/{oid}")
- client.assert_ok("查询订单详情失败")
- detail = client.extract_data() or {}
- status = detail.get("orderStatus", "")
- print(f" [OK] 订单详情 orderId={oid} status={status}")
- def test_76_order_pay(self, client, member_token):
- """订单支付(mock模式)"""
- oid = getattr(client, '_test_order_id', None)
- if not oid:
- pytest.skip("无订单ID")
- resp = client.post(f"/api/order/{oid}/pay")
- data = client.assert_ok("支付失败")
- print(f" [OK] 支付调用成功")
- def test_77_order_confirm_receive(self, client, member_token):
- """确认收货"""
- oid = getattr(client, '_test_order_id', None)
- if not oid:
- pytest.skip("无订单ID")
- resp = client.post(f"/api/order/{oid}/confirm-receive")
- data = client.assert_ok("确认收货失败")
- print(f" [OK] 确认收货 orderId={oid}")
- # ============================================================
- # 开放统计 (MallStatsOpenController)
- # ============================================================
- class TestConsumerStats:
- def test_78_stats_hot_category(self, client):
- """热销类目统计"""
- resp = client.get("/api/open/stats/hotCategory")
- client.assert_ok("查询热销类目失败")
- data = client.extract_data() or {}
- print(f" [OK] 热销类目: {json.dumps(data, ensure_ascii=False)[:100]}")
- def test_79_stats_overview(self, client):
- """商城概览统计"""
- resp = client.get("/api/open/stats/overview")
- client.assert_ok("查询商城概览失败")
- data = client.extract_data() or {}
- print(f" [OK] 商城概览: {json.dumps(data, ensure_ascii=False)[:100]}")
|