loginLogic_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. package pub
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "testing"
  7. "time"
  8. permModel "perms-system-server/internal/model/perm"
  9. productModel "perms-system-server/internal/model/product"
  10. productmemberModel "perms-system-server/internal/model/productmember"
  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/types"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. func newTestSvcCtx() *svc.ServiceContext {
  20. return svc.NewServiceContext(testutil.GetTestConfig())
  21. }
  22. func setupCaptcha(t *testing.T) (string, string) {
  23. t.Helper()
  24. id := "captcha_" + testutil.UniqueId()
  25. code := "1234"
  26. defaultCaptchaStore.Set(id, code)
  27. return id, code
  28. }
  29. func insertTestUser(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, username, password string, status, isSuperAdmin int64) (int64, func()) {
  30. t.Helper()
  31. conn := testutil.GetTestSqlConn()
  32. now := time.Now().Unix()
  33. hashed := testutil.HashPassword(password)
  34. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  35. Username: username,
  36. Password: hashed,
  37. Nickname: username,
  38. Avatar: sql.NullString{},
  39. Email: username + "@test.com",
  40. Phone: "13800000000",
  41. Remark: "",
  42. DeptId: 0,
  43. IsSuperAdmin: isSuperAdmin,
  44. MustChangePassword: 2,
  45. Status: status,
  46. CreateTime: now,
  47. UpdateTime: now,
  48. })
  49. require.NoError(t, err)
  50. id, _ := res.LastInsertId()
  51. cleanup := func() {
  52. testutil.CleanTable(ctx, conn, "`sys_user`", id)
  53. }
  54. return id, cleanup
  55. }
  56. func insertTestProduct(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, code, appKey, appSecret string) (int64, func()) {
  57. t.Helper()
  58. conn := testutil.GetTestSqlConn()
  59. now := time.Now().Unix()
  60. res, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  61. Code: code,
  62. Name: code,
  63. AppKey: appKey,
  64. AppSecret: appSecret,
  65. Status: 1,
  66. CreateTime: now,
  67. UpdateTime: now,
  68. })
  69. require.NoError(t, err)
  70. id, _ := res.LastInsertId()
  71. cleanup := func() {
  72. testutil.CleanTable(ctx, conn, "`sys_product`", id)
  73. }
  74. return id, cleanup
  75. }
  76. // TC-0001: 正常登录(普通用户+productCode)
  77. func TestLogin_NormalWithProductCodeBasic(t *testing.T) {
  78. ctx := context.Background()
  79. svcCtx := newCaptchaDisabledSvcCtx()
  80. conn := testutil.GetTestSqlConn()
  81. username := testutil.UniqueId()
  82. password := "TestPass123"
  83. pc := testutil.UniqueId()
  84. now := time.Now().Unix()
  85. captchaId, captchaCode := setupCaptcha(t)
  86. userId, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  87. t.Cleanup(cleanUser)
  88. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  89. t.Cleanup(cleanProduct)
  90. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  91. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now,
  92. })
  93. require.NoError(t, err)
  94. pmId, _ := pmRes.LastInsertId()
  95. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) })
  96. logic := NewLoginLogic(ctx, svcCtx)
  97. resp, err := logic.Login(&types.LoginReq{
  98. Username: username,
  99. Password: password,
  100. ProductCode: pc,
  101. CaptchaId: captchaId,
  102. CaptchaCode: captchaCode,
  103. })
  104. require.NoError(t, err)
  105. require.NotNil(t, resp)
  106. assert.NotEmpty(t, resp.AccessToken)
  107. assert.NotEmpty(t, resp.RefreshToken)
  108. assert.True(t, resp.Expires > time.Now().Unix(), "expires应为未来的unix时间戳")
  109. assert.Equal(t, username, resp.UserInfo.Username)
  110. assert.Equal(t, "MEMBER", resp.UserInfo.MemberType)
  111. }
  112. // TC-0002: 正常登录-带productCode
  113. func TestLogin_NormalWithProductCode(t *testing.T) {
  114. ctx := context.Background()
  115. svcCtx := newCaptchaDisabledSvcCtx()
  116. conn := testutil.GetTestSqlConn()
  117. username := testutil.UniqueId()
  118. password := "TestPass123"
  119. pc := testutil.UniqueId()
  120. now := time.Now().Unix()
  121. captchaId, captchaCode := setupCaptcha(t)
  122. userId, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  123. t.Cleanup(cleanUser)
  124. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  125. t.Cleanup(cleanProduct)
  126. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  127. ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 1, CreateTime: now, UpdateTime: now,
  128. })
  129. require.NoError(t, err)
  130. pmId, _ := pmRes.LastInsertId()
  131. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) })
  132. permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  133. ProductCode: pc, Name: "perm1", Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now,
  134. })
  135. require.NoError(t, err)
  136. permId, _ := permRes.LastInsertId()
  137. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permId) })
  138. logic := NewLoginLogic(ctx, svcCtx)
  139. resp, err := logic.Login(&types.LoginReq{
  140. Username: username,
  141. Password: password,
  142. ProductCode: pc,
  143. CaptchaId: captchaId,
  144. CaptchaCode: captchaCode,
  145. })
  146. require.NoError(t, err)
  147. require.NotNil(t, resp)
  148. assert.Equal(t, "ADMIN", resp.UserInfo.MemberType)
  149. assert.NotEmpty(t, resp.UserInfo.Perms)
  150. }
  151. // TC-0003: 超管通过产品端登录被拒绝
  152. func TestLogin_SuperAdminRejected(t *testing.T) {
  153. ctx := context.Background()
  154. svcCtx := newCaptchaDisabledSvcCtx()
  155. username := testutil.UniqueId()
  156. password := "TestPass123"
  157. pc := testutil.UniqueId()
  158. captchaId, captchaCode := setupCaptcha(t)
  159. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 1)
  160. t.Cleanup(cleanUser)
  161. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  162. t.Cleanup(cleanProduct)
  163. logic := NewLoginLogic(ctx, svcCtx)
  164. resp, err := logic.Login(&types.LoginReq{
  165. Username: username,
  166. Password: password,
  167. ProductCode: pc,
  168. CaptchaId: captchaId,
  169. CaptchaCode: captchaCode,
  170. })
  171. require.Nil(t, resp)
  172. require.Error(t, err)
  173. var codeErr *response.CodeError
  174. require.True(t, errors.As(err, &codeErr))
  175. assert.Equal(t, 403, codeErr.Code())
  176. assert.Equal(t, "超级管理员不允许通过产品端登录,请使用管理后台", codeErr.Error())
  177. }
  178. // TC-0004: 超管无productCode被拒绝
  179. func TestLogin_SuperAdminWithoutProductCodeRejected(t *testing.T) {
  180. ctx := context.Background()
  181. svcCtx := newCaptchaDisabledSvcCtx()
  182. username := testutil.UniqueId()
  183. password := "TestPass123"
  184. captchaId, captchaCode := setupCaptcha(t)
  185. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 1)
  186. t.Cleanup(cleanUser)
  187. logic := NewLoginLogic(ctx, svcCtx)
  188. resp, err := logic.Login(&types.LoginReq{
  189. Username: username,
  190. Password: password,
  191. CaptchaId: captchaId,
  192. CaptchaCode: captchaCode,
  193. })
  194. require.Nil(t, resp)
  195. require.Error(t, err)
  196. var codeErr *response.CodeError
  197. require.True(t, errors.As(err, &codeErr))
  198. assert.Equal(t, 403, codeErr.Code())
  199. assert.Equal(t, "超级管理员不允许通过产品端登录,请使用管理后台", codeErr.Error())
  200. }
  201. // TC-0005: 用户不存在
  202. func TestLogin_UserNotFound(t *testing.T) {
  203. ctx := context.Background()
  204. svcCtx := newCaptchaDisabledSvcCtx()
  205. captchaId, captchaCode := setupCaptcha(t)
  206. logic := NewLoginLogic(ctx, svcCtx)
  207. resp, err := logic.Login(&types.LoginReq{
  208. Username: "nonexistent_" + testutil.UniqueId(),
  209. Password: "whatever",
  210. CaptchaId: captchaId,
  211. CaptchaCode: captchaCode,
  212. })
  213. require.Nil(t, resp)
  214. require.Error(t, err)
  215. var codeErr *response.CodeError
  216. require.True(t, errors.As(err, &codeErr))
  217. assert.Equal(t, 401, codeErr.Code())
  218. assert.Equal(t, "用户名或密码错误", codeErr.Error())
  219. }
  220. // TC-0007: 密码错误
  221. func TestLogin_WrongPassword(t *testing.T) {
  222. ctx := context.Background()
  223. svcCtx := newCaptchaDisabledSvcCtx()
  224. username := testutil.UniqueId()
  225. captchaId, captchaCode := setupCaptcha(t)
  226. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, "CorrectPass", 1, 2)
  227. t.Cleanup(cleanUser)
  228. logic := NewLoginLogic(ctx, svcCtx)
  229. resp, err := logic.Login(&types.LoginReq{
  230. Username: username,
  231. Password: "WrongPass",
  232. CaptchaId: captchaId,
  233. CaptchaCode: captchaCode,
  234. })
  235. require.Nil(t, resp)
  236. require.Error(t, err)
  237. var codeErr *response.CodeError
  238. require.True(t, errors.As(err, &codeErr))
  239. assert.Equal(t, 401, codeErr.Code())
  240. assert.Equal(t, "用户名或密码错误", codeErr.Error())
  241. }
  242. // TC-0008: 账号冻结
  243. func TestLogin_AccountFrozen(t *testing.T) {
  244. ctx := context.Background()
  245. svcCtx := newCaptchaDisabledSvcCtx()
  246. username := testutil.UniqueId()
  247. password := "TestPass123"
  248. captchaId, captchaCode := setupCaptcha(t)
  249. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 2, 2)
  250. t.Cleanup(cleanUser)
  251. logic := NewLoginLogic(ctx, svcCtx)
  252. resp, err := logic.Login(&types.LoginReq{
  253. Username: username,
  254. Password: password,
  255. CaptchaId: captchaId,
  256. CaptchaCode: captchaCode,
  257. })
  258. require.Nil(t, resp)
  259. require.Error(t, err)
  260. var codeErr *response.CodeError
  261. require.True(t, errors.As(err, &codeErr))
  262. assert.Equal(t, 403, codeErr.Code())
  263. assert.Equal(t, "账号已被冻结", codeErr.Error())
  264. }
  265. // TC-0009: 非产品成员
  266. func TestLogin_NonMemberWithProductCode(t *testing.T) {
  267. ctx := context.Background()
  268. svcCtx := newCaptchaDisabledSvcCtx()
  269. username := testutil.UniqueId()
  270. password := "TestPass123"
  271. pc := testutil.UniqueId()
  272. captchaId, captchaCode := setupCaptcha(t)
  273. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  274. t.Cleanup(cleanUser)
  275. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  276. t.Cleanup(cleanProduct)
  277. logic := NewLoginLogic(ctx, svcCtx)
  278. resp, err := logic.Login(&types.LoginReq{
  279. Username: username,
  280. Password: password,
  281. ProductCode: pc,
  282. CaptchaId: captchaId,
  283. CaptchaCode: captchaCode,
  284. })
  285. require.Nil(t, resp)
  286. require.Error(t, err)
  287. var codeErr *response.CodeError
  288. require.True(t, errors.As(err, &codeErr))
  289. assert.Equal(t, 403, codeErr.Code())
  290. // loginService 去除重复 FindOneByProductCodeUserId,所有非成员/禁用成员分支合并
  291. assert.Equal(t, "您不是该产品的有效成员", codeErr.Error())
  292. }
  293. // TC-0010: DEVELOPER成员
  294. func TestLogin_DeveloperMember(t *testing.T) {
  295. ctx := context.Background()
  296. svcCtx := newCaptchaDisabledSvcCtx()
  297. conn := testutil.GetTestSqlConn()
  298. username := testutil.UniqueId()
  299. password := "TestPass123"
  300. pc := testutil.UniqueId()
  301. now := time.Now().Unix()
  302. captchaId, captchaCode := setupCaptcha(t)
  303. userId, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  304. t.Cleanup(cleanUser)
  305. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  306. t.Cleanup(cleanProduct)
  307. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  308. ProductCode: pc, UserId: userId, MemberType: "DEVELOPER", Status: 1, CreateTime: now, UpdateTime: now,
  309. })
  310. require.NoError(t, err)
  311. pmId, _ := pmRes.LastInsertId()
  312. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) })
  313. permCode := testutil.UniqueId()
  314. permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  315. ProductCode: pc, Name: "dev_perm", Code: permCode, Status: 1, CreateTime: now, UpdateTime: now,
  316. })
  317. require.NoError(t, err)
  318. permId, _ := permRes.LastInsertId()
  319. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permId) })
  320. logic := NewLoginLogic(ctx, svcCtx)
  321. resp, err := logic.Login(&types.LoginReq{
  322. Username: username,
  323. Password: password,
  324. ProductCode: pc,
  325. CaptchaId: captchaId,
  326. CaptchaCode: captchaCode,
  327. })
  328. require.NoError(t, err)
  329. require.NotNil(t, resp)
  330. assert.Equal(t, "DEVELOPER", resp.UserInfo.MemberType)
  331. assert.Contains(t, resp.UserInfo.Perms, permCode)
  332. }
  333. // TC-0011: SQL注入
  334. func TestLogin_SQLInjection(t *testing.T) {
  335. ctx := context.Background()
  336. svcCtx := newCaptchaDisabledSvcCtx()
  337. captchaId, captchaCode := setupCaptcha(t)
  338. logic := NewLoginLogic(ctx, svcCtx)
  339. resp, err := logic.Login(&types.LoginReq{
  340. Username: "' OR 1=1 --",
  341. Password: "anything",
  342. CaptchaId: captchaId,
  343. CaptchaCode: captchaCode,
  344. })
  345. require.Nil(t, resp)
  346. require.Error(t, err)
  347. var codeErr *response.CodeError
  348. require.True(t, errors.As(err, &codeErr))
  349. assert.Equal(t, 401, codeErr.Code())
  350. assert.Equal(t, "用户名或密码错误", codeErr.Error())
  351. }
  352. // TC-0013: 产品成员被禁用时拒绝登录(修复验证)
  353. func TestLogin_DisabledMemberRejected(t *testing.T) {
  354. ctx := context.Background()
  355. svcCtx := newCaptchaDisabledSvcCtx()
  356. conn := testutil.GetTestSqlConn()
  357. username := testutil.UniqueId()
  358. password := "TestPass123"
  359. pc := testutil.UniqueId()
  360. now := time.Now().Unix()
  361. captchaId, captchaCode := setupCaptcha(t)
  362. userId, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  363. t.Cleanup(cleanUser)
  364. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  365. t.Cleanup(cleanProduct)
  366. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  367. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 2, CreateTime: now, UpdateTime: now,
  368. })
  369. require.NoError(t, err)
  370. pmId, _ := pmRes.LastInsertId()
  371. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) })
  372. logic := NewLoginLogic(ctx, svcCtx)
  373. resp, err := logic.Login(&types.LoginReq{
  374. Username: username,
  375. Password: password,
  376. ProductCode: pc,
  377. CaptchaId: captchaId,
  378. CaptchaCode: captchaCode,
  379. })
  380. require.Nil(t, resp)
  381. require.Error(t, err)
  382. var codeErr2 *response.CodeError
  383. require.True(t, errors.As(err, &codeErr2))
  384. assert.Equal(t, 403, codeErr2.Code())
  385. // 禁用成员在 loadMembership 阶段即被清空 MemberType,与"非成员"文案合并
  386. assert.Equal(t, "您不是该产品的有效成员", codeErr2.Error())
  387. }
  388. // TC-0014: 产品已被禁用时拒绝登录
  389. func TestLogin_DisabledProductRejected(t *testing.T) {
  390. ctx := context.Background()
  391. svcCtx := newCaptchaDisabledSvcCtx()
  392. conn := testutil.GetTestSqlConn()
  393. username := testutil.UniqueId()
  394. password := "TestPass123"
  395. pc := testutil.UniqueId()
  396. now := time.Now().Unix()
  397. captchaId, captchaCode := setupCaptcha(t)
  398. _, cleanUser := insertTestUser(t, ctx, svcCtx, username, password, 1, 2)
  399. t.Cleanup(cleanUser)
  400. pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  401. Code: pc, Name: pc, AppKey: testutil.UniqueId(), AppSecret: "secret",
  402. Status: 2, CreateTime: now, UpdateTime: now,
  403. })
  404. require.NoError(t, err)
  405. pId, _ := pRes.LastInsertId()
  406. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product`", pId) })
  407. logic := NewLoginLogic(ctx, svcCtx)
  408. resp, err := logic.Login(&types.LoginReq{
  409. Username: username,
  410. Password: password,
  411. ProductCode: pc,
  412. CaptchaId: captchaId,
  413. CaptchaCode: captchaCode,
  414. })
  415. require.Nil(t, resp)
  416. require.Error(t, err)
  417. var codeErr3 *response.CodeError
  418. require.True(t, errors.As(err, &codeErr3))
  419. assert.Equal(t, 403, codeErr3.Code())
  420. assert.Equal(t, "该产品已被禁用", codeErr3.Error())
  421. }