package integration import ( "bytes" "context" "encoding/json" "fmt" "net/http/httptest" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" testcontainers_postgres "github.com/testcontainers/testcontainers-go/modules/postgres" testcontainers_redis "github.com/testcontainers/testcontainers-go/modules/redis" "github.com/testcontainers/testcontainers-go/wait" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/break/junhong_cmp_fiber/internal/bootstrap" "github.com/break/junhong_cmp_fiber/internal/handler" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" roleService "github.com/break/junhong_cmp_fiber/internal/service/role" postgresStore "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" "github.com/break/junhong_cmp_fiber/pkg/response" ) // roleTestEnv 角色测试环境 type roleTestEnv struct { db *gorm.DB redisClient *redis.Client app *fiber.App roleService *roleService.Service postgresCleanup func() redisCleanup func() } // setupRoleTestEnv 设置角色测试环境 func setupRoleTestEnv(t *testing.T) *roleTestEnv { t.Helper() ctx := context.Background() // 启动 PostgreSQL 容器 pgContainer, err := testcontainers_postgres.RunContainer(ctx, testcontainers.WithImage("postgres:14-alpine"), testcontainers_postgres.WithDatabase("testdb"), testcontainers_postgres.WithUsername("postgres"), testcontainers_postgres.WithPassword("password"), testcontainers.WithWaitStrategy( wait.ForLog("database system is ready to accept connections"). WithOccurrence(2). WithStartupTimeout(30*time.Second), ), ) require.NoError(t, err, "启动 PostgreSQL 容器失败") pgConnStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) // 启动 Redis 容器 redisContainer, err := testcontainers_redis.RunContainer(ctx, testcontainers.WithImage("redis:6-alpine"), ) require.NoError(t, err, "启动 Redis 容器失败") redisHost, err := redisContainer.Host(ctx) require.NoError(t, err) redisPort, err := redisContainer.MappedPort(ctx, "6379") require.NoError(t, err) // 连接数据库 db, err := gorm.Open(postgres.Open(pgConnStr), &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{}, ) require.NoError(t, err) // 连接 Redis redisClient := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%s", redisHost, redisPort.Port()), }) // 初始化 Store roleStore := postgresStore.NewRoleStore(db) permissionStore := postgresStore.NewPermissionStore(db) rolePermissionStore := postgresStore.NewRolePermissionStore(db) // 初始化 Service roleSvc := roleService.New(roleStore, permissionStore, rolePermissionStore) // 初始化 Handler roleHandler := handler.NewRoleHandler(roleSvc) // 创建 Fiber App app := fiber.New(fiber.Config{ ErrorHandler: func(c *fiber.Ctx, err error) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) }, }) // 注册路由 services := &bootstrap.Handlers{ Role: roleHandler, } routes.RegisterRoutes(app, services) return &roleTestEnv{ db: db, redisClient: redisClient, app: app, roleService: roleSvc, postgresCleanup: func() { if err := pgContainer.Terminate(ctx); err != nil { t.Logf("终止 PostgreSQL 容器失败: %v", err) } }, redisCleanup: func() { if err := redisContainer.Terminate(ctx); err != nil { t.Logf("终止 Redis 容器失败: %v", err) } }, } } // teardown 清理测试环境 func (e *roleTestEnv) teardown() { if e.postgresCleanup != nil { e.postgresCleanup() } if e.redisCleanup != nil { e.redisCleanup() } } // TestRoleAPI_Create 测试创建角色 API func TestRoleAPI_Create(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) t.Run("成功创建角色", func(t *testing.T) { reqBody := model.CreateRoleRequest{ RoleName: "测试角色", RoleDesc: "这是一个测试角色", RoleType: constants.RoleTypeSuper, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/v1/roles", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) // 验证数据库中角色已创建 var count int64 env.db.Model(&model.Role{}).Where("role_name = ?", "测试角色").Count(&count) assert.Equal(t, int64(1), count) }) t.Run("缺少必填字段返回错误", func(t *testing.T) { reqBody := map[string]interface{}{ "role_desc": "缺少名称", } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/v1/roles", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) require.NoError(t, err) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.NotEqual(t, 0, result.Code) }) } // TestRoleAPI_Get 测试获取角色详情 API func TestRoleAPI_Get(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建测试角色 testRole := &model.Role{ RoleName: "获取测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) t.Run("成功获取角色详情", func(t *testing.T) { req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, 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/v1/roles/99999", nil) resp, err := env.app.Test(req) require.NoError(t, err) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, errors.CodeRoleNotFound, result.Code) }) } // TestRoleAPI_Update 测试更新角色 API func TestRoleAPI_Update(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建测试角色 testRole := &model.Role{ RoleName: "更新测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) t.Run("成功更新角色", func(t *testing.T) { newName := "更新后角色" reqBody := model.UpdateRoleRequest{ RoleName: &newName, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 验证数据库已更新 var updated model.Role env.db.First(&updated, testRole.ID) assert.Equal(t, newName, updated.RoleName) }) } // TestRoleAPI_Delete 测试删除角色 API func TestRoleAPI_Delete(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) t.Run("成功软删除角色", func(t *testing.T) { // 创建测试角色 testRole := &model.Role{ RoleName: "删除测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 验证角色已软删除 var deleted model.Role err = env.db.Unscoped().First(&deleted, testRole.ID).Error require.NoError(t, err) assert.NotNil(t, deleted.DeletedAt) }) } // TestRoleAPI_List 测试角色列表 API func TestRoleAPI_List(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建多个测试角色 for i := 1; i <= 5; i++ { role := &model.Role{ RoleName: fmt.Sprintf("列表测试角色_%d", i), RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(role) } t.Run("成功获取角色列表", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/v1/roles?page=1&page_size=10", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) }) } // TestRoleAPI_AssignPermissions 测试分配权限 API func TestRoleAPI_AssignPermissions(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建测试角色 testRole := &model.Role{ RoleName: "权限分配测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) // 创建测试权限 testPerm := &model.Permission{ PermName: "测试权限", PermCode: "test:permission", PermType: constants.PermissionTypeMenu, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testPerm) t.Run("成功分配权限", func(t *testing.T) { reqBody := model.AssignPermissionsRequest{ PermIDs: []uint{testPerm.ID}, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/roles/%d/permissions", testRole.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 验证关联已创建 var count int64 env.db.Model(&model.RolePermission{}).Where("role_id = ? AND perm_id = ?", testRole.ID, testPerm.ID).Count(&count) assert.Equal(t, int64(1), count) }) } // TestRoleAPI_GetPermissions 测试获取角色权限 API func TestRoleAPI_GetPermissions(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建测试角色 testRole := &model.Role{ RoleName: "获取权限测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) // 创建并分配权限 testPerm := &model.Permission{ PermName: "获取权限测试", PermCode: "get:permission:test", PermType: constants.PermissionTypeMenu, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testPerm) rolePerm := &model.RolePermission{ RoleID: testRole.ID, PermID: testPerm.ID, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(rolePerm) t.Run("成功获取角色权限", func(t *testing.T) { req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d/permissions", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) }) } // TestRoleAPI_RemovePermission 测试移除权限 API func TestRoleAPI_RemovePermission(t *testing.T) { env := setupRoleTestEnv(t) defer env.teardown() // 添加测试中间件 testUserID := uint(1) env.app.Use(func(c *fiber.Ctx) error { ctx := middleware.SetUserContext(c.UserContext(), testUserID, constants.UserTypeRoot, 0) c.SetUserContext(ctx) return c.Next() }) // 创建测试角色 testRole := &model.Role{ RoleName: "移除权限测试角色", RoleType: constants.RoleTypeSuper, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testRole) // 创建并分配权限 testPerm := &model.Permission{ PermName: "移除权限测试", PermCode: "remove:permission:test", PermType: constants.PermissionTypeMenu, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(testPerm) rolePerm := &model.RolePermission{ RoleID: testRole.ID, PermID: testPerm.ID, Status: constants.StatusEnabled, Creator: 1, Updater: 1, } env.db.Create(rolePerm) t.Run("成功移除权限", func(t *testing.T) { req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/roles/%d/permissions/%d", testRole.ID, testPerm.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 验证关联已软删除 var rp model.RolePermission err = env.db.Unscoped().Where("role_id = ? AND perm_id = ?", testRole.ID, testPerm.ID).First(&rp).Error require.NoError(t, err) assert.NotNil(t, rp.DeletedAt) }) }