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