package integration import ( "bytes" "context" "encoding/json" "fmt" "net/http/httptest" "testing" "time" "github.com/break/junhong_cmp_fiber/internal/bootstrap" internalMiddleware "github.com/break/junhong_cmp_fiber/internal/middleware" "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/constants" pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm" "github.com/break/junhong_cmp_fiber/pkg/queue" "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" ) type packageTestEnv struct { db *gorm.DB rdb *redis.Client tokenManager *auth.TokenManager app *fiber.App adminToken string t *testing.T } func setupPackageTestEnv(t *testing.T) *packageTestEnv { t.Helper() t.Setenv("JUNHONG_DATABASE_HOST", "cxd.whcxd.cn") t.Setenv("JUNHONG_DATABASE_PORT", "16159") t.Setenv("JUNHONG_DATABASE_USER", "erp_pgsql") t.Setenv("JUNHONG_DATABASE_PASSWORD", "erp_2025") t.Setenv("JUNHONG_DATABASE_DBNAME", "junhong_cmp_test") t.Setenv("JUNHONG_REDIS_ADDRESS", "cxd.whcxd.cn") t.Setenv("JUNHONG_REDIS_PORT", "16299") t.Setenv("JUNHONG_REDIS_PASSWORD", "cpNbWtAaqgo1YJmbMp3h") t.Setenv("JUNHONG_JWT_SECRET_KEY", "test_secret_key_for_integration_tests") 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) rdb := redis.NewClient(&redis.Options{ Addr: "cxd.whcxd.cn:16299", Password: "cpNbWtAaqgo1YJmbMp3h", DB: 15, }) ctx := context.Background() err = rdb.Ping(ctx).Err() require.NoError(t, err) testPrefix := fmt.Sprintf("test:%s:", t.Name()) keys, _ := rdb.Keys(ctx, testPrefix+"*").Result() if len(keys) > 0 { rdb.Del(ctx, keys...) } tokenManager := auth.NewTokenManager(rdb, 24*time.Hour, 7*24*time.Hour) superAdmin := testutil.CreateSuperAdmin(t, db) adminToken, _ := testutil.GenerateTestToken(t, rdb, superAdmin, "web") queueClient := queue.NewClient(rdb, zapLogger) deps := &bootstrap.Dependencies{ DB: db, Redis: rdb, Logger: zapLogger, TokenManager: tokenManager, QueueClient: queueClient, } result, err := bootstrap.Bootstrap(deps) require.NoError(t, err) app := fiber.New(fiber.Config{ ErrorHandler: internalMiddleware.ErrorHandler(zapLogger), }) routes.RegisterRoutes(app, result.Handlers, result.Middlewares) return &packageTestEnv{ db: db, rdb: rdb, tokenManager: tokenManager, app: app, adminToken: adminToken, t: t, } } func (e *packageTestEnv) teardown() { e.db.Exec("DELETE FROM tb_package WHERE package_code LIKE 'TEST%'") e.db.Exec("DELETE FROM tb_package_series WHERE series_code LIKE 'TEST%'") ctx := context.Background() testPrefix := fmt.Sprintf("test:%s:", e.t.Name()) keys, _ := e.rdb.Keys(ctx, testPrefix+"*").Result() if len(keys) > 0 { e.rdb.Del(ctx, keys...) } e.rdb.Close() } // ==================== Part 1: 套餐系列 API 测试 ==================== func TestPackageSeriesAPI_Create(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesCode := fmt.Sprintf("TEST_SERIES_%d", timestamp) body := map[string]interface{}{ "series_code": seriesCode, "series_name": "测试套餐系列", "description": "API集成测试创建的套餐系列", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("POST", "/api/admin/package-series", bytes.NewReader(jsonBody)) 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) dataMap, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, seriesCode, dataMap["series_code"]) assert.Equal(t, "测试套餐系列", dataMap["series_name"]) assert.Equal(t, float64(constants.StatusEnabled), dataMap["status"]) t.Logf("创建的套餐系列 ID: %v", dataMap["id"]) } func TestPackageSeriesAPI_Get(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesCode := fmt.Sprintf("TEST_SERIES_%d", timestamp) series := &model.PackageSeries{ SeriesCode: seriesCode, SeriesName: "测试套餐系列", Description: "测试描述", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(series).Error) url := fmt.Sprintf("/api/admin/package-series/%d", series.ID) req := httptest.NewRequest("GET", url, 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 := result.Data.(map[string]interface{}) assert.Equal(t, seriesCode, dataMap["series_code"]) assert.Equal(t, "测试套餐系列", dataMap["series_name"]) } func TestPackageSeriesAPI_List(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesList := []*model.PackageSeries{ { SeriesCode: fmt.Sprintf("TEST_LIST_%d_001", timestamp), SeriesName: "列表测试系列1", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1}, }, { SeriesCode: fmt.Sprintf("TEST_LIST_%d_002", timestamp), SeriesName: "列表测试系列2", Status: constants.StatusEnabled, BaseModel: model.BaseModel{Creator: 1}, }, } for _, s := range seriesList { require.NoError(t, env.db.Create(s).Error) } req := httptest.NewRequest("GET", "/api/admin/package-series?page=1&page_size=20", 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) } func TestPackageSeriesAPI_Update(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesCode := fmt.Sprintf("TEST_SERIES_%d", timestamp) series := &model.PackageSeries{ SeriesCode: seriesCode, SeriesName: "原始系列名称", Description: "原始描述", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(series).Error) body := map[string]interface{}{ "series_name": "更新后的系列名称", "description": "更新后的描述", } jsonBody, _ := json.Marshal(body) url := fmt.Sprintf("/api/admin/package-series/%d", series.ID) req := httptest.NewRequest("PUT", url, bytes.NewReader(jsonBody)) 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) dataMap := result.Data.(map[string]interface{}) assert.Equal(t, "更新后的系列名称", dataMap["series_name"]) assert.Equal(t, "更新后的描述", dataMap["description"]) } func TestPackageSeriesAPI_UpdateStatus(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesCode := fmt.Sprintf("TEST_SERIES_%d", timestamp) series := &model.PackageSeries{ SeriesCode: seriesCode, SeriesName: "测试系列", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(series).Error) body := map[string]interface{}{ "status": constants.StatusDisabled, } jsonBody, _ := json.Marshal(body) url := fmt.Sprintf("/api/admin/package-series/%d/status", series.ID) req := httptest.NewRequest("PATCH", url, bytes.NewReader(jsonBody)) 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) var updatedSeries model.PackageSeries env.db.First(&updatedSeries, series.ID) assert.Equal(t, constants.StatusDisabled, updatedSeries.Status) } func TestPackageSeriesAPI_Delete(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() seriesCode := fmt.Sprintf("TEST_SERIES_%d", timestamp) series := &model.PackageSeries{ SeriesCode: seriesCode, SeriesName: "测试系列", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(series).Error) url := fmt.Sprintf("/api/admin/package-series/%d", series.ID) req := httptest.NewRequest("DELETE", url, 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) var deletedSeries model.PackageSeries err = env.db.First(&deletedSeries, series.ID).Error assert.Error(t, err, "删除后应查不到套餐系列") } // ==================== Part 2: 套餐 API 测试 ==================== func TestPackageAPI_Create(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) body := map[string]interface{}{ "package_code": packageCode, "package_name": "测试套餐", "package_type": "formal", "duration_months": 12, "price": 99900, "data_type": "real", "real_data_mb": 10240, } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("POST", "/api/admin/packages", bytes.NewReader(jsonBody)) 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) dataMap, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, packageCode, dataMap["package_code"]) assert.Equal(t, "测试套餐", dataMap["package_name"]) assert.Equal(t, float64(constants.StatusEnabled), dataMap["status"]) assert.Equal(t, float64(2), dataMap["shelf_status"]) // 默认下架 t.Logf("创建的套餐 ID: %v", dataMap["id"]) } func TestPackageAPI_UpdateStatus_DisableForceOffShelf(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) // 先创建套餐 createBody := map[string]interface{}{ "package_code": packageCode, "package_name": "测试套餐", "package_type": "formal", "duration_months": 12, "price": 99900, } jsonBody, _ := json.Marshal(createBody) createReq := httptest.NewRequest("POST", "/api/admin/packages", bytes.NewReader(jsonBody)) createReq.Header.Set("Content-Type", "application/json") createReq.Header.Set("Authorization", "Bearer "+env.adminToken) createResp, err := env.app.Test(createReq, -1) require.NoError(t, err) defer createResp.Body.Close() var createResult response.Response err = json.NewDecoder(createResp.Body).Decode(&createResult) require.NoError(t, err) require.Equal(t, 0, createResult.Code) dataMap := createResult.Data.(map[string]interface{}) pkgID := uint(dataMap["id"].(float64)) // 先上架套餐 shelfBody := map[string]interface{}{ "shelf_status": 1, } shelfJsonBody, _ := json.Marshal(shelfBody) shelfReq := httptest.NewRequest("PATCH", fmt.Sprintf("/api/admin/packages/%d/shelf", pkgID), bytes.NewReader(shelfJsonBody)) shelfReq.Header.Set("Content-Type", "application/json") shelfReq.Header.Set("Authorization", "Bearer "+env.adminToken) shelfResp, err := env.app.Test(shelfReq, -1) require.NoError(t, err) defer shelfResp.Body.Close() // 禁用套餐 disableBody := map[string]interface{}{ "status": constants.StatusDisabled, } disableJsonBody, _ := json.Marshal(disableBody) disableReq := httptest.NewRequest("PATCH", fmt.Sprintf("/api/admin/packages/%d/status", pkgID), bytes.NewReader(disableJsonBody)) disableReq.Header.Set("Content-Type", "application/json") disableReq.Header.Set("Authorization", "Bearer "+env.adminToken) disableResp, err := env.app.Test(disableReq, -1) require.NoError(t, err) defer disableResp.Body.Close() var disableResult response.Response err = json.NewDecoder(disableResp.Body).Decode(&disableResult) require.NoError(t, err) t.Logf("禁用响应: 状态码=%d, 错误码=%d, 消息=%s", disableResp.StatusCode, disableResult.Code, disableResult.Message) require.Equal(t, 200, disableResp.StatusCode, "禁用套餐应该成功") require.Equal(t, 0, disableResult.Code, "禁用套餐应该返回成功") // 验证禁用后自动下架 var updatedPkg model.Package ctx := pkgGorm.SkipDataPermission(context.Background()) require.NoError(t, env.db.WithContext(ctx).First(&updatedPkg, pkgID).Error) assert.Equal(t, constants.StatusDisabled, updatedPkg.Status, "套餐应该被禁用") assert.Equal(t, 2, updatedPkg.ShelfStatus, "禁用时应该强制下架") t.Logf("禁用套餐后,状态: %d, 上架状态: %d", updatedPkg.Status, updatedPkg.ShelfStatus) } func TestPackageAPI_UpdateShelfStatus_DisabledCannotOnShelf(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) // 先创建套餐 createBody := map[string]interface{}{ "package_code": packageCode, "package_name": "测试套餐", "package_type": "formal", "duration_months": 12, "price": 99900, } jsonBody, _ := json.Marshal(createBody) createReq := httptest.NewRequest("POST", "/api/admin/packages", bytes.NewReader(jsonBody)) createReq.Header.Set("Content-Type", "application/json") createReq.Header.Set("Authorization", "Bearer "+env.adminToken) createResp, err := env.app.Test(createReq, -1) require.NoError(t, err) defer createResp.Body.Close() var createResult response.Response err = json.NewDecoder(createResp.Body).Decode(&createResult) require.NoError(t, err) require.Equal(t, 0, createResult.Code) dataMap := createResult.Data.(map[string]interface{}) pkgID := uint(dataMap["id"].(float64)) // 禁用套餐 disableBody := map[string]interface{}{ "status": constants.StatusDisabled, } disableJsonBody, _ := json.Marshal(disableBody) disableReq := httptest.NewRequest("PATCH", fmt.Sprintf("/api/admin/packages/%d/status", pkgID), bytes.NewReader(disableJsonBody)) disableReq.Header.Set("Content-Type", "application/json") disableReq.Header.Set("Authorization", "Bearer "+env.adminToken) disableResp, err := env.app.Test(disableReq, -1) require.NoError(t, err) defer disableResp.Body.Close() var disableResult response.Response err = json.NewDecoder(disableResp.Body).Decode(&disableResult) require.NoError(t, err) t.Logf("禁用响应: 状态码=%d, 错误码=%d, 消息=%s", disableResp.StatusCode, disableResult.Code, disableResult.Message) require.Equal(t, 200, disableResp.StatusCode, "禁用套餐应该成功") require.Equal(t, 0, disableResult.Code, "禁用套餐应该返回成功") // 尝试上架禁用的套餐 shelfBody := map[string]interface{}{ "shelf_status": 1, } shelfJsonBody, _ := json.Marshal(shelfBody) shelfReq := httptest.NewRequest("PATCH", fmt.Sprintf("/api/admin/packages/%d/shelf", pkgID), bytes.NewReader(shelfJsonBody)) shelfReq.Header.Set("Content-Type", "application/json") shelfReq.Header.Set("Authorization", "Bearer "+env.adminToken) shelfResp, err := env.app.Test(shelfReq, -1) require.NoError(t, err) defer shelfResp.Body.Close() // 应该返回错误 var result response.Response err = json.NewDecoder(shelfResp.Body).Decode(&result) require.NoError(t, err) assert.NotEqual(t, 0, result.Code, "禁用的套餐不能上架,应返回错误码") // 验证套餐仍然是下架状态 var unchangedPkg model.Package ctx := pkgGorm.SkipDataPermission(context.Background()) require.NoError(t, env.db.WithContext(ctx).First(&unchangedPkg, pkgID).Error) assert.Equal(t, 2, unchangedPkg.ShelfStatus, "禁用的套餐应该保持下架状态") t.Logf("尝试上架禁用套餐失败,错误码: %d, 消息: %s", result.Code, result.Message) } func TestPackageAPI_Get(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) pkg := &model.Package{ PackageCode: packageCode, PackageName: "测试套餐", PackageType: "formal", DurationMonths: 12, Price: 99900, Status: constants.StatusEnabled, ShelfStatus: 2, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(pkg).Error) url := fmt.Sprintf("/api/admin/packages/%d", pkg.ID) req := httptest.NewRequest("GET", url, 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 := result.Data.(map[string]interface{}) assert.Equal(t, packageCode, dataMap["package_code"]) assert.Equal(t, "测试套餐", dataMap["package_name"]) } func TestPackageAPI_List(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() pkgList := []*model.Package{ { PackageCode: fmt.Sprintf("TEST_LIST_%d_001", timestamp), PackageName: "列表测试套餐1", PackageType: "formal", DurationMonths: 12, Price: 99900, Status: constants.StatusEnabled, ShelfStatus: 1, BaseModel: model.BaseModel{Creator: 1}, }, { PackageCode: fmt.Sprintf("TEST_LIST_%d_002", timestamp), PackageName: "列表测试套餐2", PackageType: "addon", DurationMonths: 1, Price: 9990, Status: constants.StatusEnabled, ShelfStatus: 2, BaseModel: model.BaseModel{Creator: 1}, }, } for _, p := range pkgList { require.NoError(t, env.db.Create(p).Error) } req := httptest.NewRequest("GET", "/api/admin/packages?page=1&page_size=20", 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) } func TestPackageAPI_Update(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) pkg := &model.Package{ PackageCode: packageCode, PackageName: "原始套餐名称", PackageType: "formal", DurationMonths: 12, Price: 99900, Status: constants.StatusEnabled, ShelfStatus: 2, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(pkg).Error) body := map[string]interface{}{ "package_name": "更新后的套餐名称", "price": 119900, } jsonBody, _ := json.Marshal(body) url := fmt.Sprintf("/api/admin/packages/%d", pkg.ID) req := httptest.NewRequest("PUT", url, bytes.NewReader(jsonBody)) 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) dataMap := result.Data.(map[string]interface{}) assert.Equal(t, "更新后的套餐名称", dataMap["package_name"]) assert.Equal(t, float64(119900), dataMap["price"]) } func TestPackageAPI_Delete(t *testing.T) { env := setupPackageTestEnv(t) defer env.teardown() timestamp := time.Now().Unix() packageCode := fmt.Sprintf("TEST_PKG_%d", timestamp) pkg := &model.Package{ PackageCode: packageCode, PackageName: "测试套餐", PackageType: "formal", DurationMonths: 12, Price: 99900, Status: constants.StatusEnabled, ShelfStatus: 2, BaseModel: model.BaseModel{ Creator: 1, }, } require.NoError(t, env.db.Create(pkg).Error) url := fmt.Sprintf("/api/admin/packages/%d", pkg.ID) req := httptest.NewRequest("DELETE", url, 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) var deletedPkg model.Package err = env.db.First(&deletedPkg, pkg.ID).Error assert.Error(t, err, "删除后应查不到套餐") }