# ============================================================ # 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]}")