package integration import ( "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" "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 deviceTestEnv struct { db *gorm.DB rdb *redis.Client tokenManager *auth.TokenManager app *fiber.App adminToken string t *testing.T } func setupDeviceTestEnv(t *testing.T) *deviceTestEnv { 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 &deviceTestEnv{ db: db, rdb: rdb, tokenManager: tokenManager, app: app, adminToken: adminToken, t: t, } } func (e *deviceTestEnv) teardown() { // 清理测试数据 e.db.Exec("DELETE FROM tb_device WHERE device_no LIKE 'TEST%'") e.db.Exec("DELETE FROM tb_device_sim_binding WHERE device_id IN (SELECT id FROM tb_device WHERE device_no LIKE 'TEST%')") e.db.Exec("DELETE FROM tb_device_import_task WHERE task_no 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() } func TestDevice_List(t *testing.T) { env := setupDeviceTestEnv(t) defer env.teardown() // 创建测试设备 devices := []*model.Device{ {DeviceNo: "TEST_DEVICE_001", DeviceName: "测试设备1", DeviceType: "router", MaxSimSlots: 4, Status: constants.DeviceStatusInStock}, {DeviceNo: "TEST_DEVICE_002", DeviceName: "测试设备2", DeviceType: "router", MaxSimSlots: 2, Status: constants.DeviceStatusInStock}, {DeviceNo: "TEST_DEVICE_003", DeviceName: "测试设备3", DeviceType: "mifi", MaxSimSlots: 1, Status: constants.DeviceStatusDistributed}, } for _, device := range devices { require.NoError(t, env.db.Create(device).Error) } t.Run("获取设备列表-无过滤", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices?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) }) t.Run("获取设备列表-按设备类型过滤", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices?device_type=router", 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) }) t.Run("获取设备列表-按状态过滤", func(t *testing.T) { req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/devices?status=%d", constants.DeviceStatusInStock), 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) }) t.Run("未认证请求应返回错误", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices", nil) 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, "未认证请求应返回错误码") }) } func TestDevice_GetByID(t *testing.T) { env := setupDeviceTestEnv(t) defer env.teardown() // 创建测试设备 device := &model.Device{ DeviceNo: "TEST_DEVICE_GET_001", DeviceName: "测试设备详情", DeviceType: "router", MaxSimSlots: 4, Status: constants.DeviceStatusInStock, } require.NoError(t, env.db.Create(device).Error) t.Run("获取设备详情-成功", func(t *testing.T) { url := fmt.Sprintf("/api/admin/devices/%d", device.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, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, "TEST_DEVICE_GET_001", dataMap["device_no"]) }) t.Run("获取不存在的设备-应返回错误", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices/999999", nil) 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, "不存在的设备应返回错误码") }) } func TestDevice_Delete(t *testing.T) { env := setupDeviceTestEnv(t) defer env.teardown() // 创建测试设备 device := &model.Device{ DeviceNo: "TEST_DEVICE_DEL_001", DeviceName: "测试删除设备", DeviceType: "router", MaxSimSlots: 4, Status: constants.DeviceStatusInStock, } require.NoError(t, env.db.Create(device).Error) t.Run("删除设备-成功", func(t *testing.T) { url := fmt.Sprintf("/api/admin/devices/%d", device.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 deletedDevice model.Device err = env.db.Unscoped().First(&deletedDevice, device.ID).Error require.NoError(t, err) assert.NotNil(t, deletedDevice.DeletedAt) }) } func TestDeviceImport_TaskList(t *testing.T) { env := setupDeviceTestEnv(t) defer env.teardown() // 创建测试导入任务 task := &model.DeviceImportTask{ TaskNo: "TEST_DEVICE_IMPORT_001", Status: model.ImportTaskStatusCompleted, BatchNo: "TEST_BATCH_001", TotalCount: 100, } require.NoError(t, env.db.Create(task).Error) t.Run("获取导入任务列表", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices/import-tasks?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) }) t.Run("获取导入任务详情", func(t *testing.T) { url := fmt.Sprintf("/api/admin/devices/import-tasks/%d", task.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) }) } func TestDevice_GetByIMEI(t *testing.T) { env := setupDeviceTestEnv(t) defer env.teardown() // 创建测试设备 device := &model.Device{ DeviceNo: "TEST_IMEI_001", DeviceName: "测试IMEI查询设备", DeviceType: "router", MaxSimSlots: 4, Status: constants.DeviceStatusInStock, } require.NoError(t, env.db.Create(device).Error) t.Run("通过IMEI查询设备详情-成功", func(t *testing.T) { url := fmt.Sprintf("/api/admin/devices/by-imei/%s", device.DeviceNo) 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, ok := result.Data.(map[string]interface{}) require.True(t, ok) assert.Equal(t, "TEST_IMEI_001", dataMap["device_no"]) assert.Equal(t, "测试IMEI查询设备", dataMap["device_name"]) }) t.Run("通过不存在的IMEI查询-应返回错误", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/admin/devices/by-imei/NONEXISTENT_IMEI", nil) 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, "不存在的IMEI应返回错误码") }) t.Run("未认证请求-应返回错误", func(t *testing.T) { url := fmt.Sprintf("/api/admin/devices/by-imei/%s", device.DeviceNo) req := httptest.NewRequest("GET", url, nil) 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, "未认证请求应返回错误码") }) }