healthapp
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1022 lines
28 KiB

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)
}
}