package integration import ( "bytes" "context" "encoding/json" "fmt" "net/http/httptest" "testing" "time" "github.com/break/junhong_cmp_fiber/internal/bootstrap" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" "github.com/break/junhong_cmp_fiber/pkg/auth" "github.com/break/junhong_cmp_fiber/pkg/config" "github.com/break/junhong_cmp_fiber/pkg/response" "github.com/break/junhong_cmp_fiber/tests/testutil" "github.com/gofiber/fiber/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) // shopManagementTestEnv 商户管理测试环境 type shopManagementTestEnv struct { db *gorm.DB redisClient *redis.Client tokenManager *auth.TokenManager app *fiber.App adminToken string superAdminUser *model.Account t *testing.T } // setupShopManagementTestEnv 设置商户管理测试环境 func setupShopManagementTestEnv(t *testing.T) *shopManagementTestEnv { t.Helper() t.Setenv("CONFIG_ENV", "dev") t.Setenv("CONFIG_PATH", "../../configs/config.dev.yaml") cfg, err := config.Load() require.NoError(t, err) err = config.Set(cfg) require.NoError(t, err) zapLogger, _ := zap.NewDevelopment() dsn := "host=cxd.whcxd.cn port=16159 user=erp_pgsql password=erp_2025 dbname=junhong_cmp_test sslmode=disable TimeZone=Asia/Shanghai" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) err = db.AutoMigrate( &model.Account{}, &model.Role{}, &model.Permission{}, &model.AccountRole{}, &model.RolePermission{}, &model.Shop{}, &model.Enterprise{}, &model.PersonalCustomer{}, ) require.NoError(t, err) redisClient := redis.NewClient(&redis.Options{ Addr: "cxd.whcxd.cn:16299", Password: "cpNbWtAaqgo1YJmbMp3h", DB: 15, }) ctx := context.Background() err = redisClient.Ping(ctx).Err() require.NoError(t, err) testPrefix := fmt.Sprintf("test:%s:", t.Name()) keys, _ := redisClient.Keys(ctx, testPrefix+"*").Result() if len(keys) > 0 { redisClient.Del(ctx, keys...) } tokenManager := auth.NewTokenManager(redisClient, 24*time.Hour, 7*24*time.Hour) superAdmin := testutil.CreateSuperAdmin(t, db) adminToken, _ := testutil.GenerateTestToken(t, redisClient, superAdmin, "web") deps := &bootstrap.Dependencies{ DB: db, Redis: redisClient, Logger: zapLogger, TokenManager: tokenManager, } result, err := bootstrap.Bootstrap(deps) require.NoError(t, err) handlers := result.Handlers middlewares := result.Middlewares app := fiber.New(fiber.Config{ ErrorHandler: func(c *fiber.Ctx, err error) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) }, }) routes.RegisterRoutes(app, handlers, middlewares) return &shopManagementTestEnv{ db: db, redisClient: redisClient, tokenManager: tokenManager, app: app, adminToken: adminToken, superAdminUser: superAdmin, t: t, } } // teardown 清理测试环境 func (e *shopManagementTestEnv) teardown() { e.db.Exec("DELETE FROM tb_account WHERE username LIKE 'test%' OR username LIKE 'agent%' OR username LIKE 'superadmin%'") e.db.Exec("DELETE FROM tb_shop WHERE shop_code LIKE 'TEST%' OR shop_code LIKE 'DUP%' OR shop_code LIKE 'SHOP_%' OR shop_code LIKE 'ORIG%' OR shop_code LIKE 'DEL%' OR shop_code LIKE 'MULTI%'") ctx := context.Background() testPrefix := fmt.Sprintf("test:%s:", e.t.Name()) keys, _ := e.redisClient.Keys(ctx, testPrefix+"*").Result() if len(keys) > 0 { e.redisClient.Del(ctx, keys...) } e.redisClient.Close() } // TestShopManagement_CreateShop 测试创建商户 func TestShopManagement_CreateShop(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() reqBody := model.CreateShopRequest{ ShopName: "测试商户", ShopCode: "TEST001", InitUsername: "testuser", InitPhone: "13800138000", InitPassword: "password123", } body, err := json.Marshal(reqBody) require.NoError(t, err) req := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() t.Logf("HTTP 状态码: %d", resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) t.Logf("响应 Code: %d, Message: %s", result.Code, result.Message) t.Logf("响应 Data: %+v", result.Data) if result.Code != 0 { t.Fatalf("API 返回错误: %s", result.Message) } assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 0, result.Code) assert.NotNil(t, result.Data) shopData, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, "测试商户", shopData["shop_name"]) assert.Equal(t, "TEST001", shopData["shop_code"]) assert.Equal(t, float64(1), shopData["level"]) assert.Equal(t, float64(1), shopData["status"]) } // TestShopManagement_CreateShop_DuplicateCode 测试创建商户 - 商户编码重复 func TestShopManagement_CreateShop_DuplicateCode(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 通过 API 创建第一个商户 firstReq := model.CreateShopRequest{ ShopName: "商户1", ShopCode: "DUP001", InitUsername: "dupuser1", InitPhone: "13800138101", InitPassword: "password123", } firstBody, _ := json.Marshal(firstReq) firstHttpReq := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(firstBody)) firstHttpReq.Header.Set("Content-Type", "application/json") firstHttpReq.Header.Set("Authorization", "Bearer "+env.adminToken) firstResp, _ := env.app.Test(firstHttpReq, -1) var firstResult response.Response json.NewDecoder(firstResp.Body).Decode(&firstResult) firstResp.Body.Close() require.Equal(t, 0, firstResult.Code, "第一个商户应该创建成功") // 尝试创建编码重复的商户 reqBody := model.CreateShopRequest{ ShopName: "商户2", ShopCode: "DUP001", // 使用相同编码 InitUsername: "dupuser2", InitPhone: "13800138102", InitPassword: "password123", } body, err := json.Marshal(reqBody) require.NoError(t, err) req := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() // 应该返回错误 var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.NotEqual(t, 0, result.Code) // 非成功状态 assert.Contains(t, result.Message, "已存在") // 错误消息应包含"已存在" } // TestShopManagement_ListShops 测试查询商户列表 func TestShopManagement_ListShops(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 创建测试数据 testutil.CreateTestShop(t, env.db, "商户A", "SHOP_A", 1, nil) testutil.CreateTestShop(t, env.db, "商户B", "SHOP_B", 1, nil) testutil.CreateTestShop(t, env.db, "商户C", "SHOP_C", 2, nil) req := httptest.NewRequest("GET", "/api/admin/shops?page=1&size=10", nil) req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) // 解析分页数据 dataMap, ok := result.Data.(map[string]interface{}) require.True(t, ok) items, ok := dataMap["items"].([]interface{}) require.True(t, ok) assert.GreaterOrEqual(t, len(items), 3) } // TestShopManagement_UpdateShop 测试更新商户 func TestShopManagement_UpdateShop(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 创建测试商户 shop := testutil.CreateTestShop(t, env.db, "原始商户", "ORIG001", 1, nil) // 更新商户 reqBody := model.UpdateShopRequest{ ShopName: "更新后的商户", Status: 1, } body, err := json.Marshal(reqBody) require.NoError(t, err) req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shops/%d", shop.ID), bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) assert.NotNil(t, result.Data) shopData, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, "更新后的商户", shopData["shop_name"]) } // TestShopManagement_DeleteShop 测试删除商户 func TestShopManagement_DeleteShop(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 创建测试商户 shop := testutil.CreateTestShop(t, env.db, "待删除商户", "DEL001", 1, nil) // 删除商户 req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil) req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) } // TestShopManagement_DeleteShop_WithMultipleAccounts 测试删除商户 - 多个关联账号 func TestShopManagement_DeleteShop_WithMultipleAccounts(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 创建测试商户 shop := testutil.CreateTestShop(t, env.db, "多账号商户", "MULTI001", 1, nil) // 删除商户 req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil) req.Header.Set("Authorization", "Bearer "+env.adminToken) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) } // TestShopManagement_Unauthorized 测试未认证访问 func TestShopManagement_Unauthorized(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 不提供 token req := httptest.NewRequest("GET", "/api/admin/shops", nil) resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() // 应该返回 401 未授权 assert.Equal(t, 401, resp.StatusCode) } // TestShopManagement_InvalidToken 测试无效 token func TestShopManagement_InvalidToken(t *testing.T) { env := setupShopManagementTestEnv(t) defer env.teardown() // 提供无效 token req := httptest.NewRequest("GET", "/api/admin/shops", nil) req.Header.Set("Authorization", "Bearer invalid-token-12345") resp, err := env.app.Test(req, -1) require.NoError(t, err) defer resp.Body.Close() // 应该返回 401 未授权 assert.Equal(t, 401, resp.StatusCode) }