package tests import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "testing" "healthapi/internal/config" "healthapi/internal/database" "github.com/zeromicro/go-zero/core/logx" "gorm.io/gorm" ) // ==================== 全局变量 ==================== const ( baseURL = "http://localhost:8080" testPhone = "13800138000" testPwd = "123456" ) var ( token string // JWT Token db *gorm.DB testUserID uint cartItemID uint cartItemID2 uint addressID uint addressID2 uint orderID uint productID uint skuID uint ) // ==================== TestMain ==================== func TestMain(m *testing.M) { logx.Disable() // 连接数据库(用于插入种子数据) var err error db, err = database.NewDB(config.DatabaseConfig{ Driver: "sqlite", DataSource: "../data/health.db", }) if err != nil { fmt.Printf("❌ 数据库连接失败: %v\n", err) os.Exit(1) } // 插入商城种子数据 if err := SeedMallTestData(db); err != nil { fmt.Printf("❌ 种子数据插入失败: %v\n", err) os.Exit(1) } fmt.Println("✅ 种子数据准备完成") code := m.Run() fmt.Println("\n========================================") fmt.Println(" 商城API测试完成") fmt.Println("========================================") os.Exit(code) } // ==================== 辅助函数 ==================== type apiResponse struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data,omitempty"` } func doRequest(method, path string, body interface{}, auth bool) (*http.Response, []byte, error) { var reqBody io.Reader if body != nil { data, _ := json.Marshal(body) reqBody = bytes.NewBuffer(data) } req, err := http.NewRequest(method, baseURL+path, reqBody) if err != nil { return nil, nil, err } req.Header.Set("Content-Type", "application/json") if auth && token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, nil, err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) return resp, respBody, nil } // parseData 智能解析响应: // - 如果响应被 {"code":0,"message":"success","data":{...}} 包装,提取 data // - 如果是直接返回 JSON,则直接解析 func parseData(raw []byte, v interface{}) error { // 先尝试作为包装格式解析 var wrapper apiResponse if err := json.Unmarshal(raw, &wrapper); err == nil && wrapper.Data != nil { // 有 data 字段,说明是包装格式 return json.Unmarshal(wrapper.Data, v) } // 否则直接解析(goctl 生成的 handler 直接返回数据) return json.Unmarshal(raw, v) } // ==================== 1. 认证测试 ==================== func TestMall_00_Login(t *testing.T) { body := map[string]string{ "phone": testPhone, "password": testPwd, } resp, respBody, err := doRequest("POST", "/api/auth/login", body, false) if err != nil { t.Fatalf("登录请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("登录状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Token string `json:"token"` User struct { ID uint `json:"id"` } `json:"user"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析登录响应失败: %v, body=%s", err, string(respBody)) } if result.Token == "" { t.Fatalf("Token 为空, body=%s", string(respBody)) } token = result.Token testUserID = result.User.ID t.Logf("✅ 登录成功, UserID=%d, Token=%s...", testUserID, token[:20]) // 清理旧测试数据 CleanMallTestData(db, testUserID) t.Log("✅ 旧测试数据已清理") } // ==================== 2. 会员系统 ==================== func TestMall_01_GetMemberInfo(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/member/info", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Level string `json:"level"` LevelName string `json:"level_name"` Points int `json:"points"` Discount float64 `json:"discount"` PointsMultiplier float64 `json:"points_multiplier"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析失败: %v", err) } t.Logf("✅ 会员信息: level=%s, name=%s, points=%d, discount=%.2f, multiplier=%.1f", result.Level, result.LevelName, result.Points, result.Discount, result.PointsMultiplier) if result.Level == "" { t.Error("会员等级为空") } if result.Discount <= 0 || result.Discount > 1 { t.Errorf("折扣率异常: %f", result.Discount) } } func TestMall_02_GetPointsRecords(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/member/points/records?page=1&page_size=10", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Records []interface{} `json:"records"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析失败: %v", err) } t.Logf("✅ 积分记录: total=%d, records=%d", result.PageInfo.Total, len(result.Records)) } // ==================== 3. 商品模块 ==================== func TestMall_10_GetCategories(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/categories", nil, false) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Categories []struct { ID uint `json:"id"` Name string `json:"name"` } `json:"categories"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析失败: %v", err) } if len(result.Categories) == 0 { t.Error("分类列表为空") } for _, c := range result.Categories { t.Logf(" 分类: id=%d, name=%s", c.ID, c.Name) } t.Logf("✅ 获取分类成功, 共 %d 个", len(result.Categories)) } func TestMall_11_GetMallProducts(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/products?page=1&page_size=10", nil, false) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Products []struct { ID uint `json:"id"` Name string `json:"name"` Price float64 `json:"price"` } `json:"products"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析失败: %v", err) } if len(result.Products) == 0 { t.Fatal("商品列表为空") } // 保存第一个商品ID用于后续测试 productID = result.Products[0].ID for _, p := range result.Products { t.Logf(" 商品: id=%d, name=%s, price=%.2f", p.ID, p.Name, p.Price) } t.Logf("✅ 商品列表: total=%d, 当前页=%d", result.PageInfo.Total, len(result.Products)) } func TestMall_12_GetMallProductDetail(t *testing.T) { if productID == 0 { t.Skip("无商品ID,跳过") } path := fmt.Sprintf("/api/mall/products/%d", productID) resp, respBody, err := doRequest("GET", path, nil, false) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` Name string `json:"name"` Price float64 `json:"price"` Description string `json:"description"` Efficacy string `json:"efficacy"` Skus []struct { ID uint `json:"id"` Name string `json:"name"` Price float64 `json:"price"` } `json:"skus"` } if err := parseData(respBody, &result); err != nil { t.Fatalf("解析失败: %v", err) } if result.Name == "" { t.Error("商品名为空") } t.Logf("✅ 商品详情: id=%d, name=%s, price=%.2f, skus=%d", result.ID, result.Name, result.Price, len(result.Skus)) // 保存 SKU ID if len(result.Skus) > 0 { skuID = result.Skus[0].ID t.Logf(" SKU: id=%d, name=%s, price=%.2f", skuID, result.Skus[0].Name, result.Skus[0].Price) } } func TestMall_13_SearchMallProducts(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/products/search?keyword=养生茶&page=1&page_size=10", nil, false) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Products []struct { Name string `json:"name"` } `json:"products"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } parseData(respBody, &result) t.Logf("✅ 搜索 '养生茶': total=%d, results=%d", result.PageInfo.Total, len(result.Products)) for _, p := range result.Products { t.Logf(" 结果: %s", p.Name) } } func TestMall_14_GetFeaturedProducts(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/products/featured?page=1&page_size=10", nil, false) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Products []struct { Name string `json:"name"` } `json:"products"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } parseData(respBody, &result) t.Logf("✅ 推荐商品: total=%d, results=%d", result.PageInfo.Total, len(result.Products)) } // ==================== 4. 购物车 ==================== func TestMall_20_AddCart(t *testing.T) { if productID == 0 { t.Skip("无商品ID") } // 添加商品(无SKU) body := map[string]interface{}{ "product_id": productID, "quantity": 2, } resp, respBody, err := doRequest("POST", "/api/mall/cart", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` ProductName string `json:"product_name"` Quantity int `json:"quantity"` Price float64 `json:"price"` } parseData(respBody, &result) cartItemID = result.ID t.Logf("✅ 添加购物车: id=%d, product=%s, qty=%d, price=%.2f", cartItemID, result.ProductName, result.Quantity, result.Price) if cartItemID == 0 { t.Fatal("购物车项ID为0") } } func TestMall_21_AddCartWithSku(t *testing.T) { if productID == 0 || skuID == 0 { t.Skip("无商品或SKU ID") } body := map[string]interface{}{ "product_id": productID, "sku_id": skuID, "quantity": 1, } resp, respBody, err := doRequest("POST", "/api/mall/cart", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` SkuName string `json:"sku_name"` Quantity int `json:"quantity"` } parseData(respBody, &result) cartItemID2 = result.ID t.Logf("✅ 添加SKU购物车: id=%d, sku=%s, qty=%d", cartItemID2, result.SkuName, result.Quantity) } func TestMall_22_GetCart(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/cart", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Items []interface{} `json:"items"` TotalCount int `json:"total_count"` TotalAmount float64 `json:"total_amount"` } parseData(respBody, &result) t.Logf("✅ 购物车: items=%d, totalCount=%d, totalAmount=%.2f", len(result.Items), result.TotalCount, result.TotalAmount) if len(result.Items) == 0 { t.Error("购物车为空") } } func TestMall_23_UpdateCart(t *testing.T) { if cartItemID == 0 { t.Skip("无购物车项ID") } body := map[string]interface{}{ "quantity": 3, "selected": true, } path := fmt.Sprintf("/api/mall/cart/%d", cartItemID) resp, respBody, err := doRequest("PUT", path, body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Quantity int `json:"quantity"` } parseData(respBody, &result) if result.Quantity != 3 { t.Errorf("数量更新失败: expected=3, got=%d", result.Quantity) } t.Logf("✅ 更新购物车数量: qty=%d", result.Quantity) } func TestMall_24_BatchSelectCart(t *testing.T) { body := map[string]interface{}{ "ids": []uint{}, "selected": true, } resp, respBody, err := doRequest("POST", "/api/mall/cart/batch-select", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } t.Log("✅ 批量全选成功") } func TestMall_25_DeleteCartItem(t *testing.T) { if cartItemID2 == 0 { t.Skip("无购物车项ID2") } path := fmt.Sprintf("/api/mall/cart/%d", cartItemID2) resp, respBody, err := doRequest("DELETE", path, nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } t.Logf("✅ 删除购物车项: id=%d", cartItemID2) } // ==================== 5. 收货地址 ==================== func TestMall_30_CreateAddress(t *testing.T) { body := map[string]interface{}{ "receiver_name": "张三", "phone": "13900139000", "province": "北京市", "city": "北京市", "district": "朝阳区", "detail_addr": "建国路88号", "postal_code": "100020", "tag": "home", } resp, respBody, err := doRequest("POST", "/api/mall/addresses", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` ReceiverName string `json:"receiver_name"` IsDefault bool `json:"is_default"` } parseData(respBody, &result) addressID = result.ID t.Logf("✅ 创建地址: id=%d, name=%s, isDefault=%v", addressID, result.ReceiverName, result.IsDefault) if !result.IsDefault { t.Error("第一个地址应自动为默认") } } func TestMall_31_CreateAddress2(t *testing.T) { body := map[string]interface{}{ "receiver_name": "李四", "phone": "13800138001", "province": "上海市", "city": "上海市", "district": "浦东新区", "detail_addr": "陆家嘴环路1号", "tag": "company", } resp, respBody, err := doRequest("POST", "/api/mall/addresses", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` } parseData(respBody, &result) addressID2 = result.ID t.Logf("✅ 创建第二个地址: id=%d", addressID2) } func TestMall_32_GetAddresses(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/addresses", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Addresses []struct { ID uint `json:"id"` ReceiverName string `json:"receiver_name"` IsDefault bool `json:"is_default"` } `json:"addresses"` } parseData(respBody, &result) if len(result.Addresses) < 2 { t.Errorf("地址数量不足: expected>=2, got=%d", len(result.Addresses)) } for _, a := range result.Addresses { t.Logf(" 地址: id=%d, name=%s, default=%v", a.ID, a.ReceiverName, a.IsDefault) } t.Logf("✅ 地址列表: %d 个", len(result.Addresses)) } func TestMall_33_GetAddress(t *testing.T) { if addressID == 0 { t.Skip("无地址ID") } path := fmt.Sprintf("/api/mall/addresses/%d", addressID) resp, respBody, err := doRequest("GET", path, nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ReceiverName string `json:"receiver_name"` Province string `json:"province"` } parseData(respBody, &result) t.Logf("✅ 地址详情: name=%s, province=%s", result.ReceiverName, result.Province) } func TestMall_34_SetDefaultAddress(t *testing.T) { if addressID2 == 0 { t.Skip("无第二地址ID") } path := fmt.Sprintf("/api/mall/addresses/%d/default", addressID2) resp, respBody, err := doRequest("PUT", path, nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } t.Logf("✅ 设置默认地址: id=%d", addressID2) } func TestMall_35_DeleteAddress(t *testing.T) { if addressID2 == 0 { t.Skip("无地址ID2") } path := fmt.Sprintf("/api/mall/addresses/%d", addressID2) resp, respBody, err := doRequest("DELETE", path, nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } t.Logf("✅ 删除地址: id=%d", addressID2) } // ==================== 6. 订单流程 ==================== func TestMall_40_PreviewOrder(t *testing.T) { if cartItemID == 0 { t.Skip("无购物车项") } body := map[string]interface{}{ "cart_item_ids": []uint{cartItemID}, "address_id": addressID, } resp, respBody, err := doRequest("POST", "/api/mall/orders/preview", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { TotalAmount float64 `json:"total_amount"` DiscountAmount float64 `json:"discount_amount"` ShippingFee float64 `json:"shipping_fee"` PayAmount float64 `json:"pay_amount"` MaxPointsUse int `json:"max_points_use"` } parseData(respBody, &result) t.Logf("✅ 订单预览: total=%.2f, discount=%.2f, shipping=%.2f, pay=%.2f, maxPoints=%d", result.TotalAmount, result.DiscountAmount, result.ShippingFee, result.PayAmount, result.MaxPointsUse) } func TestMall_41_CreateOrder(t *testing.T) { if cartItemID == 0 || addressID == 0 { t.Skip("缺少购物车项或地址") } body := map[string]interface{}{ "address_id": addressID, "cart_item_ids": []uint{cartItemID}, "points_used": 0, "remark": "测试订单", } resp, respBody, err := doRequest("POST", "/api/mall/orders", body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` OrderNo string `json:"order_no"` Status string `json:"status"` PayAmount float64 `json:"pay_amount"` PointsEarned int `json:"points_earned"` Items []struct { ProductName string `json:"product_name"` Quantity int `json:"quantity"` } `json:"items"` } parseData(respBody, &result) orderID = result.ID if result.OrderNo == "" { t.Error("订单号为空") } if result.Status != "pending" { t.Errorf("订单状态异常: expected=pending, got=%s", result.Status) } t.Logf("✅ 创建订单: id=%d, no=%s, status=%s, pay=%.2f, pointsEarned=%d", orderID, result.OrderNo, result.Status, result.PayAmount, result.PointsEarned) for _, item := range result.Items { t.Logf(" 商品: %s x %d", item.ProductName, item.Quantity) } } func TestMall_42_GetOrders(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/orders?page=1&page_size=10", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Orders []struct { ID uint `json:"id"` OrderNo string `json:"order_no"` Status string `json:"status"` } `json:"orders"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } parseData(respBody, &result) if len(result.Orders) == 0 { t.Error("订单列表为空") } t.Logf("✅ 订单列表: total=%d", result.PageInfo.Total) } func TestMall_43_GetOrder(t *testing.T) { if orderID == 0 { t.Skip("无订单ID") } path := fmt.Sprintf("/api/mall/orders/%d", orderID) resp, respBody, err := doRequest("GET", path, nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { ID uint `json:"id"` OrderNo string `json:"order_no"` Status string `json:"status"` ReceiverName string `json:"receiver_name"` ReceiverAddr string `json:"receiver_addr"` } parseData(respBody, &result) t.Logf("✅ 订单详情: no=%s, status=%s, receiver=%s, addr=%s", result.OrderNo, result.Status, result.ReceiverName, result.ReceiverAddr) } func TestMall_44_PayOrder(t *testing.T) { if orderID == 0 { t.Skip("无订单ID") } body := map[string]interface{}{ "pay_method": "wechat", } path := fmt.Sprintf("/api/mall/orders/%d/pay", orderID) resp, respBody, err := doRequest("POST", path, body, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Code int `json:"code"` Message string `json:"message"` } parseData(respBody, &result) t.Logf("✅ 支付订单: code=%d, msg=%s", result.Code, result.Message) // 验证支付后会员信息更新 _, memberBody, _ := doRequest("GET", "/api/mall/member/info", nil, true) var member struct { Level string `json:"level"` TotalSpent float64 `json:"total_spent"` Points int `json:"points"` } parseData(memberBody, &member) t.Logf(" 支付后会员: level=%s, totalSpent=%.2f, points=%d", member.Level, member.TotalSpent, member.Points) if member.TotalSpent <= 0 { t.Error("支付后累计消费应大于0") } if member.Points <= 0 { t.Error("支付后积分应大于0") } } func TestMall_45_GetPointsAfterPay(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/member/points/records?page=1&page_size=10", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Records []struct { Type string `json:"type"` Points int `json:"points"` Balance int `json:"balance"` Remark string `json:"remark"` } `json:"records"` } parseData(respBody, &result) if len(result.Records) == 0 { t.Error("支付后应有积分记录") } for _, r := range result.Records { t.Logf(" 积分记录: type=%s, points=%d, balance=%d, remark=%s", r.Type, r.Points, r.Balance, r.Remark) } t.Logf("✅ 积分记录: %d 条", len(result.Records)) } // ==================== 7. 订单取消测试 ==================== func TestMall_50_CreateAndCancelOrder(t *testing.T) { // 先添加一个商品到购物车 // 获取第二个商品 _, productsBody, _ := doRequest("GET", "/api/mall/products?page=1&page_size=10", nil, false) var productsResult struct { Products []struct { ID uint `json:"id"` } `json:"products"` } parseData(productsBody, &productsResult) if len(productsResult.Products) < 2 { t.Skip("商品不足2个") } testProductID := productsResult.Products[1].ID // 添加到购物车 cartBody := map[string]interface{}{ "product_id": testProductID, "quantity": 1, } _, cartResp, _ := doRequest("POST", "/api/mall/cart", cartBody, true) var cartResult struct { ID uint `json:"id"` } parseData(cartResp, &cartResult) testCartItemID := cartResult.ID if testCartItemID == 0 { t.Fatal("添加购物车失败") } // 创建订单 orderBody := map[string]interface{}{ "address_id": addressID, "cart_item_ids": []uint{testCartItemID}, "remark": "待取消测试订单", } _, orderResp, _ := doRequest("POST", "/api/mall/orders", orderBody, true) var orderResult struct { ID uint `json:"id"` OrderNo string `json:"order_no"` } parseData(orderResp, &orderResult) cancelOrderID := orderResult.ID if cancelOrderID == 0 { t.Fatal("创建订单失败") } t.Logf(" 创建待取消订单: id=%d, no=%s", cancelOrderID, orderResult.OrderNo) // 取消订单 cancelBody := map[string]interface{}{ "reason": "测试取消", } path := fmt.Sprintf("/api/mall/orders/%d/cancel", cancelOrderID) resp, respBody, err := doRequest("POST", path, cancelBody, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } // 验证订单状态 orderPath := fmt.Sprintf("/api/mall/orders/%d", cancelOrderID) _, detailBody, _ := doRequest("GET", orderPath, nil, true) var detail struct { Status string `json:"status"` CancelReason string `json:"cancel_reason"` } parseData(detailBody, &detail) if detail.Status != "cancelled" { t.Errorf("订单状态异常: expected=cancelled, got=%s", detail.Status) } t.Logf("✅ 取消订单: status=%s, reason=%s", detail.Status, detail.CancelReason) } // ==================== 8. 体质推荐 ==================== func TestMall_60_ConstitutionRecommend(t *testing.T) { resp, respBody, err := doRequest("GET", "/api/mall/products/constitution-recommend", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Products []struct { Name string `json:"name"` } `json:"products"` Constitution string `json:"constitution"` Reason string `json:"reason"` } parseData(respBody, &result) t.Logf("✅ 体质推荐: constitution=%s, reason=%s, products=%d", result.Constitution, result.Reason, len(result.Products)) for _, p := range result.Products { t.Logf(" 推荐: %s", p.Name) } } // ==================== 9. 购物车清空 ==================== func TestMall_70_ClearCart(t *testing.T) { resp, respBody, err := doRequest("DELETE", "/api/mall/cart/clear", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } // 验证购物车为空 _, cartBody, _ := doRequest("GET", "/api/mall/cart", nil, true) var cartResult struct { Items []interface{} `json:"items"` TotalCount int `json:"total_count"` } parseData(cartBody, &cartResult) if len(cartResult.Items) != 0 { t.Errorf("购物车未清空: items=%d", len(cartResult.Items)) } t.Log("✅ 购物车已清空") } // ==================== 10. 按状态筛选订单 ==================== func TestMall_80_GetOrdersByStatus(t *testing.T) { // 查询已支付订单 resp, respBody, err := doRequest("GET", "/api/mall/orders?status=paid&page=1&page_size=10", nil, true) if err != nil { t.Fatalf("请求失败: %v", err) } if resp.StatusCode != 200 { t.Fatalf("状态码=%d, body=%s", resp.StatusCode, string(respBody)) } var result struct { Orders []struct { Status string `json:"status"` } `json:"orders"` PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } parseData(respBody, &result) for _, o := range result.Orders { if o.Status != "paid" { t.Errorf("筛选结果包含非已支付订单: status=%s", o.Status) } } t.Logf("✅ 已支付订单: %d 个", result.PageInfo.Total) // 查询已取消订单 resp2, respBody2, _ := doRequest("GET", "/api/mall/orders?status=cancelled&page=1&page_size=10", nil, true) if resp2.StatusCode == 200 { var result2 struct { PageInfo struct { Total int64 `json:"total"` } `json:"page_info"` } parseData(respBody2, &result2) t.Logf("✅ 已取消订单: %d 个", result2.PageInfo.Total) } }