Files
junhong_cmp_fiber/tests/integration/account_test.go
huang d66323487b refactor: align framework cleanup with new bootstrap flow
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2025-11-19 12:47:25 +08:00

634 lines
18 KiB
Go

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"
accountService "github.com/break/junhong_cmp_fiber/internal/service/account"
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"
)
// testEnv 测试环境
type testEnv struct {
db *gorm.DB
redisClient *redis.Client
app *fiber.App
accountService *accountService.Service
postgresCleanup func()
redisCleanup func()
}
// setupTestEnv 设置测试环境
func setupTestEnv(t *testing.T) *testEnv {
t.Helper()
ctx := context.Background()
// 启动 PostgreSQL 容器
pgContainer, err := testcontainers_postgres.Run(ctx,
"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.Run(ctx,
"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
accountStore := postgresStore.NewAccountStore(db, redisClient)
roleStore := postgresStore.NewRoleStore(db)
accountRoleStore := postgresStore.NewAccountRoleStore(db)
// 初始化 Service
accService := accountService.New(accountStore, roleStore, accountRoleStore)
// 初始化 Handler
accountHandler := handler.NewAccountHandler(accService)
// 创建 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{
Account: accountHandler,
}
routes.RegisterRoutes(app, services)
return &testEnv{
db: db,
redisClient: redisClient,
app: app,
accountService: accService,
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)
}
},
}
}
// teardownTestEnv 清理测试环境
func (e *testEnv) teardown() {
if e.postgresCleanup != nil {
e.postgresCleanup()
}
if e.redisCleanup != nil {
e.redisCleanup()
}
}
// createTestAccount 创建测试账号并返回,用于设置测试上下文
func createTestAccount(t *testing.T, db *gorm.DB, account *model.Account) *model.Account {
t.Helper()
err := db.Create(account).Error
require.NoError(t, err)
return account
}
// TestAccountAPI_Create 测试创建账号 API
func TestAccountAPI_Create(t *testing.T) {
env := setupTestEnv(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()
})
// 创建一个 root 账号作为创建者
rootAccount := &model.Account{
Username: "root",
Phone: "13800000000",
Password: "hashedpassword",
UserType: constants.UserTypeRoot,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, rootAccount)
t.Run("成功创建平台账号", func(t *testing.T) {
reqBody := model.CreateAccountRequest{
Username: "platform_user",
Phone: "13800000001",
Password: "Password123",
UserType: constants.UserTypePlatform,
ParentID: &rootAccount.ID,
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", "/api/v1/accounts", 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.Account{}).Where("username = ?", "platform_user").Count(&count)
assert.Equal(t, int64(1), count)
})
t.Run("用户名重复时返回错误", func(t *testing.T) {
// 先创建一个账号
existingAccount := &model.Account{
Username: "existing_user",
Phone: "13800000002",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
ParentID: &rootAccount.ID,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, existingAccount)
// 尝试创建同名账号
reqBody := model.CreateAccountRequest{
Username: "existing_user",
Phone: "13800000003",
Password: "Password123",
UserType: constants.UserTypePlatform,
ParentID: &rootAccount.ID,
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", "/api/v1/accounts", 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.Equal(t, errors.CodeUsernameExists, result.Code)
})
t.Run("非root用户缺少parent_id时返回错误", func(t *testing.T) {
reqBody := model.CreateAccountRequest{
Username: "no_parent_user",
Phone: "13800000004",
Password: "Password123",
UserType: constants.UserTypePlatform,
// 没有提供 ParentID
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", "/api/v1/accounts", 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.Equal(t, errors.CodeParentIDRequired, result.Code)
})
}
// TestAccountAPI_Get 测试获取账号详情 API
func TestAccountAPI_Get(t *testing.T) {
env := setupTestEnv(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()
})
// 创建测试账号
testAccount := &model.Account{
Username: "test_user",
Phone: "13800000010",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
t.Run("成功获取账号详情", func(t *testing.T) {
req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d", testAccount.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/accounts/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.CodeAccountNotFound, result.Code)
})
t.Run("无效ID返回错误", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/v1/accounts/invalid", 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.CodeInvalidParam, result.Code)
})
}
// TestAccountAPI_Update 测试更新账号 API
func TestAccountAPI_Update(t *testing.T) {
env := setupTestEnv(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()
})
// 创建测试账号
testAccount := &model.Account{
Username: "update_test",
Phone: "13800000020",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
t.Run("成功更新账号", func(t *testing.T) {
newUsername := "updated_user"
reqBody := model.UpdateAccountRequest{
Username: &newUsername,
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d", testAccount.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.Account
env.db.First(&updated, testAccount.ID)
assert.Equal(t, newUsername, updated.Username)
})
}
// TestAccountAPI_Delete 测试删除账号 API
func TestAccountAPI_Delete(t *testing.T) {
env := setupTestEnv(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) {
// 创建测试账号
testAccount := &model.Account{
Username: "delete_test",
Phone: "13800000030",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d", testAccount.ID), nil)
resp, err := env.app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
// 验证账号已软删除
var deleted model.Account
err = env.db.Unscoped().First(&deleted, testAccount.ID).Error
require.NoError(t, err)
assert.NotNil(t, deleted.DeletedAt)
})
}
// TestAccountAPI_List 测试账号列表 API
func TestAccountAPI_List(t *testing.T) {
env := setupTestEnv(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++ {
account := &model.Account{
Username: fmt.Sprintf("list_test_%d", i),
Phone: fmt.Sprintf("1380000004%d", i),
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, account)
}
t.Run("成功获取账号列表", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/v1/accounts?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)
})
t.Run("分页功能正常", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/v1/accounts?page=1&page_size=2", nil)
resp, err := env.app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
})
}
// TestAccountAPI_AssignRoles 测试分配角色 API
func TestAccountAPI_AssignRoles(t *testing.T) {
env := setupTestEnv(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()
})
// 创建测试账号
testAccount := &model.Account{
Username: "role_test",
Phone: "13800000050",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
// 创建测试角色
testRole := &model.Role{
RoleName: "测试角色",
RoleType: constants.RoleTypeSuper,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
env.db.Create(testRole)
t.Run("成功分配角色", func(t *testing.T) {
reqBody := model.AssignRolesRequest{
RoleIDs: []uint{testRole.ID},
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/roles", testAccount.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.AccountRole{}).Where("account_id = ? AND role_id = ?", testAccount.ID, testRole.ID).Count(&count)
assert.Equal(t, int64(1), count)
})
}
// TestAccountAPI_GetRoles 测试获取账号角色 API
func TestAccountAPI_GetRoles(t *testing.T) {
env := setupTestEnv(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()
})
// 创建测试账号
testAccount := &model.Account{
Username: "get_roles_test",
Phone: "13800000060",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
// 创建并分配角色
testRole := &model.Role{
RoleName: "获取角色测试",
RoleType: constants.RoleTypeSuper,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
env.db.Create(testRole)
accountRole := &model.AccountRole{
AccountID: testAccount.ID,
RoleID: testRole.ID,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
env.db.Create(accountRole)
t.Run("成功获取账号角色", func(t *testing.T) {
req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/roles", testAccount.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)
})
}
// TestAccountAPI_RemoveRole 测试移除角色 API
func TestAccountAPI_RemoveRole(t *testing.T) {
env := setupTestEnv(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()
})
// 创建测试账号
testAccount := &model.Account{
Username: "remove_role_test",
Phone: "13800000070",
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
createTestAccount(t, env.db, testAccount)
// 创建并分配角色
testRole := &model.Role{
RoleName: "移除角色测试",
RoleType: constants.RoleTypeSuper,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
env.db.Create(testRole)
accountRole := &model.AccountRole{
AccountID: testAccount.ID,
RoleID: testRole.ID,
Status: constants.StatusEnabled,
Creator: 1,
Updater: 1,
}
env.db.Create(accountRole)
t.Run("成功移除角色", func(t *testing.T) {
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/roles/%d", testAccount.ID, testRole.ID), nil)
resp, err := env.app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
// 验证关联已软删除
var ar model.AccountRole
err = env.db.Unscoped().Where("account_id = ? AND role_id = ?", testAccount.ID, testRole.ID).First(&ar).Error
require.NoError(t, err)
assert.NotNil(t, ar.DeletedAt)
})
}