| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- package user
- import (
- "context"
- "database/sql"
- "errors"
- "strings"
- "sync"
- "testing"
- "time"
- deptModel "perms-system-server/internal/model/dept"
- userModel "perms-system-server/internal/model/user"
- "perms-system-server/internal/response"
- "perms-system-server/internal/svc"
- "perms-system-server/internal/testutil"
- "perms-system-server/internal/testutil/ctxhelper"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func insertTestUser(t *testing.T, ctx context.Context, username, password string) int64 {
- t.Helper()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- now := time.Now().Unix()
- res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: username,
- Password: password,
- Nickname: "test",
- Avatar: sql.NullString{},
- Email: username + "@test.com",
- Phone: "13800000000",
- Remark: "",
- DeptId: 0,
- IsSuperAdmin: 2,
- MustChangePassword: 2,
- Status: 1,
- CreateTime: now,
- UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- return id
- }
- func insertTestUserFull(t *testing.T, ctx context.Context, u *userModel.SysUser) int64 {
- t.Helper()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- now := time.Now().Unix()
- if u.CreateTime == 0 {
- u.CreateTime = now
- }
- if u.UpdateTime == 0 {
- u.UpdateTime = now
- }
- res, err := svcCtx.SysUserModel.Insert(ctx, u)
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- return id
- }
- func strPtr(s string) *string { return &s }
- func int64Ptr(i int64) *int64 { return &i }
- // TC-0122: 正常创建
- func TestCreateUser_Success(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123456",
- Nickname: "测试用户",
- Email: username + "@test.com",
- Phone: "13800138000",
- Remark: "集成测试",
- DeptId: 0,
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- assert.Greater(t, resp.Id, int64(0))
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, username, user.Username)
- assert.Equal(t, "测试用户", user.Nickname)
- assert.Equal(t, int64(1), user.Status)
- assert.Equal(t, int64(2), user.IsSuperAdmin)
- }
- // TC-0123: 用户名已存在(预检)
- func TestCreateUser_UsernameExists(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("pass123")
- userId := insertTestUser(t, ctx, username, hashed)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass456",
- })
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 409, codeErr.Code())
- assert.Equal(t, "用户名已存在", codeErr.Error())
- }
- // TC-0125: 非法email格式
- func TestCreateUser_InvalidEmail(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: testutil.UniqueId(),
- Password: "pass123",
- Email: "not-an-email",
- })
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Equal(t, "邮箱格式不正确", codeErr.Error())
- }
- // TC-0126: 合法email
- func TestCreateUser_ValidEmail(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123",
- Email: username + "@example.com",
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, username+"@example.com", user.Email)
- }
- // TC-0127: email为空(可选)
- func TestCreateUser_EmptyEmailSkipsValidation(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123",
- Email: "",
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, "", user.Email)
- }
- // TC-0128: 非法phone格式
- func TestCreateUser_InvalidPhone(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: testutil.UniqueId(),
- Password: "pass123",
- Phone: "abc",
- })
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Equal(t, "手机号格式不正确", codeErr.Error())
- }
- // TC-0129: 合法phone(国际)
- func TestCreateUser_ValidPhone(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123",
- Phone: "13900139000",
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, "13900139000", user.Phone)
- }
- // TC-0130: phone为空(可选)
- func TestCreateUser_EmptyPhoneSkipsValidation(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123",
- Phone: "",
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, "", user.Phone)
- }
- // TC-0131: 并发同username(TOCTOU)
- func TestCreateUser_ConcurrentSameUsername(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- t.Cleanup(func() {
- testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", username)
- })
- var wg sync.WaitGroup
- results := make(chan error, 2)
- for i := 0; i < 2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123456",
- Nickname: "并发测试用户",
- })
- results <- err
- }()
- }
- wg.Wait()
- close(results)
- var errs []error
- for err := range results {
- errs = append(errs, err)
- }
- require.Len(t, errs, 2)
- successCount := 0
- failCount := 0
- for _, err := range errs {
- if err == nil {
- successCount++
- } else {
- failCount++
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr), "error should be CodeError, got: %v", err)
- assert.Equal(t, 409, codeErr.Code())
- assert.Equal(t, "用户名已存在", codeErr.Error())
- }
- }
- assert.Equal(t, 1, successCount)
- assert.Equal(t, 1, failCount)
- }
- // TC-0129: 合法phone(国际)
- func TestCreateUser_ValidInternationalPhone(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123",
- Phone: "+8613800138000",
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, "+8613800138000", user.Phone)
- }
- // TC-0133: 密码少于6字符
- func TestCreateUser_PasswordTooShort(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: testutil.UniqueId(),
- Password: "12345",
- })
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Equal(t, "密码长度不能少于6个字符", codeErr.Error())
- }
- // TC-0134: 密码超过72字符
- func TestCreateUser_PasswordTooLong(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- longPwd := strings.Repeat("a", 73)
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{
- Username: testutil.UniqueId(),
- Password: longPwd,
- })
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Equal(t, "密码长度不能超过72个字符", codeErr.Error())
- }
- // TC-0516: createUser非管理员拒绝
- func TestCreateUser_MemberRejected(t *testing.T) {
- ctx := ctxhelper.MemberCtx("test_product")
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewCreateUserLogic(ctx, svcCtx)
- _, err := logic.CreateUser(&types.CreateUserReq{Username: "test", Password: "pass123"})
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0124: 带完整可选字段
- func TestCreateUser_AllOptionalFields(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- deptRes, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
- ParentId: 0,
- Name: "tc0103_dept_" + testutil.UniqueId(),
- Path: "/",
- Sort: 1,
- DeptType: "NORMAL",
- Remark: "",
- Status: 1,
- CreateTime: now,
- UpdateTime: now,
- })
- require.NoError(t, err)
- deptId, err := deptRes.LastInsertId()
- require.NoError(t, err)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Password: "pass123456",
- Nickname: "全字段用户",
- Email: username + "@example.com",
- Phone: "13900001111",
- Remark: "TC-0124完整字段",
- DeptId: deptId,
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- assert.Greater(t, resp.Id, int64(0))
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, username, user.Username)
- assert.Equal(t, "全字段用户", user.Nickname)
- assert.Equal(t, username+"@example.com", user.Email)
- assert.Equal(t, "13900001111", user.Phone)
- assert.Equal(t, "TC-0124完整字段", user.Remark)
- assert.Equal(t, deptId, user.DeptId)
- assert.Equal(t, int64(1), user.Status)
- assert.Equal(t, int64(2), user.IsSuperAdmin)
- }
|