createUserLogic_test.go 12 KB

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