createUserLogic_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. package user
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "sync"
  7. "testing"
  8. "time"
  9. deptModel "perms-system-server/internal/model/dept"
  10. userModel "perms-system-server/internal/model/user"
  11. "perms-system-server/internal/response"
  12. "perms-system-server/internal/svc"
  13. "perms-system-server/internal/testutil"
  14. "perms-system-server/internal/testutil/ctxhelper"
  15. "perms-system-server/internal/types"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. func insertTestUser(t *testing.T, ctx context.Context, username, password string) int64 {
  20. t.Helper()
  21. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  22. now := time.Now().Unix()
  23. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  24. Username: username,
  25. Password: password,
  26. Nickname: "test",
  27. Avatar: sql.NullString{},
  28. Email: username + "@test.com",
  29. Phone: "13800000000",
  30. Remark: "",
  31. DeptId: 0,
  32. IsSuperAdmin: 2,
  33. MustChangePassword: 2,
  34. Status: 1,
  35. CreateTime: now,
  36. UpdateTime: now,
  37. })
  38. require.NoError(t, err)
  39. id, _ := res.LastInsertId()
  40. return id
  41. }
  42. func insertTestUserFull(t *testing.T, ctx context.Context, u *userModel.SysUser) int64 {
  43. t.Helper()
  44. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  45. now := time.Now().Unix()
  46. if u.CreateTime == 0 {
  47. u.CreateTime = now
  48. }
  49. if u.UpdateTime == 0 {
  50. u.UpdateTime = now
  51. }
  52. res, err := svcCtx.SysUserModel.Insert(ctx, u)
  53. require.NoError(t, err)
  54. id, _ := res.LastInsertId()
  55. return id
  56. }
  57. func strPtr(s string) *string { return &s }
  58. func int64Ptr(i int64) *int64 { return &i }
  59. // TC-0101: 正常创建
  60. func TestCreateUser_Success(t *testing.T) {
  61. ctx := ctxhelper.SuperAdminCtx()
  62. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  63. conn := testutil.GetTestSqlConn()
  64. username := testutil.UniqueId()
  65. logic := NewCreateUserLogic(ctx, svcCtx)
  66. resp, err := logic.CreateUser(&types.CreateUserReq{
  67. Username: username,
  68. Password: "pass123456",
  69. Nickname: "测试用户",
  70. Email: username + "@test.com",
  71. Phone: "13800138000",
  72. Remark: "集成测试",
  73. DeptId: 0,
  74. })
  75. require.NoError(t, err)
  76. require.NotNil(t, resp)
  77. assert.Greater(t, resp.Id, int64(0))
  78. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  79. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  80. require.NoError(t, err)
  81. assert.Equal(t, username, user.Username)
  82. assert.Equal(t, "测试用户", user.Nickname)
  83. assert.Equal(t, int64(1), user.Status)
  84. assert.Equal(t, int64(2), user.IsSuperAdmin)
  85. }
  86. // TC-0102: 用户名已存在(预检)
  87. func TestCreateUser_UsernameExists(t *testing.T) {
  88. ctx := ctxhelper.SuperAdminCtx()
  89. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  90. conn := testutil.GetTestSqlConn()
  91. username := testutil.UniqueId()
  92. hashed := testutil.HashPassword("pass123")
  93. userId := insertTestUser(t, ctx, username, hashed)
  94. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  95. logic := NewCreateUserLogic(ctx, svcCtx)
  96. _, err := logic.CreateUser(&types.CreateUserReq{
  97. Username: username,
  98. Password: "pass456",
  99. })
  100. require.Error(t, err)
  101. var codeErr *response.CodeError
  102. require.True(t, errors.As(err, &codeErr))
  103. assert.Equal(t, 409, codeErr.Code())
  104. assert.Equal(t, "用户名已存在", codeErr.Error())
  105. }
  106. // TC-0104: 非法email格式
  107. func TestCreateUser_InvalidEmail(t *testing.T) {
  108. ctx := ctxhelper.SuperAdminCtx()
  109. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  110. logic := NewCreateUserLogic(ctx, svcCtx)
  111. _, err := logic.CreateUser(&types.CreateUserReq{
  112. Username: testutil.UniqueId(),
  113. Password: "pass123",
  114. Email: "not-an-email",
  115. })
  116. require.Error(t, err)
  117. var codeErr *response.CodeError
  118. require.True(t, errors.As(err, &codeErr))
  119. assert.Equal(t, 400, codeErr.Code())
  120. assert.Equal(t, "邮箱格式不正确", codeErr.Error())
  121. }
  122. // TC-0105: 合法email
  123. func TestCreateUser_ValidEmail(t *testing.T) {
  124. ctx := ctxhelper.SuperAdminCtx()
  125. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  126. conn := testutil.GetTestSqlConn()
  127. username := testutil.UniqueId()
  128. logic := NewCreateUserLogic(ctx, svcCtx)
  129. resp, err := logic.CreateUser(&types.CreateUserReq{
  130. Username: username,
  131. Password: "pass123",
  132. Email: username + "@example.com",
  133. })
  134. require.NoError(t, err)
  135. require.NotNil(t, resp)
  136. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  137. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  138. require.NoError(t, err)
  139. assert.Equal(t, username+"@example.com", user.Email)
  140. }
  141. // TC-0106: email为空(可选)
  142. func TestCreateUser_EmptyEmailSkipsValidation(t *testing.T) {
  143. ctx := ctxhelper.SuperAdminCtx()
  144. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  145. conn := testutil.GetTestSqlConn()
  146. username := testutil.UniqueId()
  147. logic := NewCreateUserLogic(ctx, svcCtx)
  148. resp, err := logic.CreateUser(&types.CreateUserReq{
  149. Username: username,
  150. Password: "pass123",
  151. Email: "",
  152. })
  153. require.NoError(t, err)
  154. require.NotNil(t, resp)
  155. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  156. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  157. require.NoError(t, err)
  158. assert.Equal(t, "", user.Email)
  159. }
  160. // TC-0107: 非法phone格式
  161. func TestCreateUser_InvalidPhone(t *testing.T) {
  162. ctx := ctxhelper.SuperAdminCtx()
  163. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  164. logic := NewCreateUserLogic(ctx, svcCtx)
  165. _, err := logic.CreateUser(&types.CreateUserReq{
  166. Username: testutil.UniqueId(),
  167. Password: "pass123",
  168. Phone: "abc",
  169. })
  170. require.Error(t, err)
  171. var codeErr *response.CodeError
  172. require.True(t, errors.As(err, &codeErr))
  173. assert.Equal(t, 400, codeErr.Code())
  174. assert.Equal(t, "手机号格式不正确", codeErr.Error())
  175. }
  176. // TC-0108: 合法phone(国际)
  177. func TestCreateUser_ValidPhone(t *testing.T) {
  178. ctx := ctxhelper.SuperAdminCtx()
  179. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  180. conn := testutil.GetTestSqlConn()
  181. username := testutil.UniqueId()
  182. logic := NewCreateUserLogic(ctx, svcCtx)
  183. resp, err := logic.CreateUser(&types.CreateUserReq{
  184. Username: username,
  185. Password: "pass123",
  186. Phone: "13900139000",
  187. })
  188. require.NoError(t, err)
  189. require.NotNil(t, resp)
  190. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  191. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  192. require.NoError(t, err)
  193. assert.Equal(t, "13900139000", user.Phone)
  194. }
  195. // TC-0109: phone为空(可选)
  196. func TestCreateUser_EmptyPhoneSkipsValidation(t *testing.T) {
  197. ctx := ctxhelper.SuperAdminCtx()
  198. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  199. conn := testutil.GetTestSqlConn()
  200. username := testutil.UniqueId()
  201. logic := NewCreateUserLogic(ctx, svcCtx)
  202. resp, err := logic.CreateUser(&types.CreateUserReq{
  203. Username: username,
  204. Password: "pass123",
  205. Phone: "",
  206. })
  207. require.NoError(t, err)
  208. require.NotNil(t, resp)
  209. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  210. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  211. require.NoError(t, err)
  212. assert.Equal(t, "", user.Phone)
  213. }
  214. // TC-0110: 并发同username(TOCTOU)
  215. func TestCreateUser_ConcurrentSameUsername(t *testing.T) {
  216. ctx := ctxhelper.SuperAdminCtx()
  217. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  218. conn := testutil.GetTestSqlConn()
  219. username := testutil.UniqueId()
  220. t.Cleanup(func() {
  221. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", username)
  222. })
  223. var wg sync.WaitGroup
  224. results := make(chan error, 2)
  225. for i := 0; i < 2; i++ {
  226. wg.Add(1)
  227. go func() {
  228. defer wg.Done()
  229. logic := NewCreateUserLogic(ctx, svcCtx)
  230. _, err := logic.CreateUser(&types.CreateUserReq{
  231. Username: username,
  232. Password: "pass123456",
  233. Nickname: "并发测试用户",
  234. })
  235. results <- err
  236. }()
  237. }
  238. wg.Wait()
  239. close(results)
  240. var errs []error
  241. for err := range results {
  242. errs = append(errs, err)
  243. }
  244. require.Len(t, errs, 2)
  245. successCount := 0
  246. failCount := 0
  247. for _, err := range errs {
  248. if err == nil {
  249. successCount++
  250. } else {
  251. failCount++
  252. var codeErr *response.CodeError
  253. require.True(t, errors.As(err, &codeErr), "error should be CodeError, got: %v", err)
  254. assert.Equal(t, 409, codeErr.Code())
  255. assert.Equal(t, "用户名已存在", codeErr.Error())
  256. }
  257. }
  258. assert.Equal(t, 1, successCount)
  259. assert.Equal(t, 1, failCount)
  260. }
  261. // TC-0108: 合法phone(国际)
  262. func TestCreateUser_ValidInternationalPhone(t *testing.T) {
  263. ctx := ctxhelper.SuperAdminCtx()
  264. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  265. conn := testutil.GetTestSqlConn()
  266. username := testutil.UniqueId()
  267. logic := NewCreateUserLogic(ctx, svcCtx)
  268. resp, err := logic.CreateUser(&types.CreateUserReq{
  269. Username: username,
  270. Password: "pass123",
  271. Phone: "+8613800138000",
  272. })
  273. require.NoError(t, err)
  274. require.NotNil(t, resp)
  275. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  276. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  277. require.NoError(t, err)
  278. assert.Equal(t, "+8613800138000", user.Phone)
  279. }
  280. // TC-0486: createUser非管理员拒绝
  281. func TestCreateUser_MemberRejected(t *testing.T) {
  282. ctx := ctxhelper.MemberCtx("test_product")
  283. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  284. logic := NewCreateUserLogic(ctx, svcCtx)
  285. _, err := logic.CreateUser(&types.CreateUserReq{Username: "test", Password: "pass123"})
  286. require.Error(t, err)
  287. var ce *response.CodeError
  288. require.True(t, errors.As(err, &ce))
  289. assert.Equal(t, 403, ce.Code())
  290. }
  291. // TC-0103: 带完整可选字段
  292. func TestCreateUser_AllOptionalFields(t *testing.T) {
  293. ctx := ctxhelper.SuperAdminCtx()
  294. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  295. conn := testutil.GetTestSqlConn()
  296. now := time.Now().Unix()
  297. deptRes, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
  298. ParentId: 0,
  299. Name: "tc0103_dept_" + testutil.UniqueId(),
  300. Path: "/",
  301. Sort: 1,
  302. DeptType: "NORMAL",
  303. Remark: "",
  304. Status: 1,
  305. CreateTime: now,
  306. UpdateTime: now,
  307. })
  308. require.NoError(t, err)
  309. deptId, err := deptRes.LastInsertId()
  310. require.NoError(t, err)
  311. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  312. username := testutil.UniqueId()
  313. logic := NewCreateUserLogic(ctx, svcCtx)
  314. resp, err := logic.CreateUser(&types.CreateUserReq{
  315. Username: username,
  316. Password: "pass123456",
  317. Nickname: "全字段用户",
  318. Email: username + "@example.com",
  319. Phone: "13900001111",
  320. Remark: "TC-0103完整字段",
  321. DeptId: deptId,
  322. })
  323. require.NoError(t, err)
  324. require.NotNil(t, resp)
  325. assert.Greater(t, resp.Id, int64(0))
  326. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  327. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  328. require.NoError(t, err)
  329. assert.Equal(t, username, user.Username)
  330. assert.Equal(t, "全字段用户", user.Nickname)
  331. assert.Equal(t, username+"@example.com", user.Email)
  332. assert.Equal(t, "13900001111", user.Phone)
  333. assert.Equal(t, "TC-0103完整字段", user.Remark)
  334. assert.Equal(t, deptId, user.DeptId)
  335. assert.Equal(t, int64(1), user.Status)
  336. assert.Equal(t, int64(2), user.IsSuperAdmin)
  337. }