removeMemberLogic_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package member
  2. import (
  3. "database/sql"
  4. "errors"
  5. "testing"
  6. "time"
  7. permModel "perms-system-server/internal/model/perm"
  8. productModel "perms-system-server/internal/model/product"
  9. memberModel "perms-system-server/internal/model/productmember"
  10. roleModel "perms-system-server/internal/model/role"
  11. userModel "perms-system-server/internal/model/user"
  12. "perms-system-server/internal/model/userperm"
  13. "perms-system-server/internal/model/userrole"
  14. "perms-system-server/internal/response"
  15. "perms-system-server/internal/svc"
  16. "perms-system-server/internal/testutil"
  17. "perms-system-server/internal/testutil/ctxhelper"
  18. "perms-system-server/internal/types"
  19. "github.com/stretchr/testify/assert"
  20. "github.com/stretchr/testify/require"
  21. )
  22. // TC-0226: 正常移除+级联(事务内)
  23. func TestRemoveMember_WithCascade(t *testing.T) {
  24. ctx := ctxhelper.SuperAdminCtx()
  25. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  26. conn := testutil.GetTestSqlConn()
  27. now := time.Now().Unix()
  28. uid := testutil.UniqueId()
  29. pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  30. Code: uid, Name: "test_prod", AppKey: uid, AppSecret: "s1",
  31. Status: 1, CreateTime: now, UpdateTime: now,
  32. })
  33. require.NoError(t, err)
  34. pId, _ := pRes.LastInsertId()
  35. uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  36. Username: uid, Password: testutil.HashPassword("pass"), Nickname: "nick",
  37. Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
  38. Status: 1, CreateTime: now, UpdateTime: now,
  39. })
  40. require.NoError(t, err)
  41. uId, _ := uRes.LastInsertId()
  42. mRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  43. ProductCode: uid, UserId: uId, MemberType: "MEMBER",
  44. Status: 1, CreateTime: now, UpdateTime: now,
  45. })
  46. require.NoError(t, err)
  47. mId, _ := mRes.LastInsertId()
  48. rRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  49. ProductCode: uid, Name: uid, Status: 1, PermsLevel: 1,
  50. CreateTime: now, UpdateTime: now,
  51. })
  52. require.NoError(t, err)
  53. rId, _ := rRes.LastInsertId()
  54. urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userrole.SysUserRole{
  55. UserId: uId, RoleId: rId, CreateTime: now, UpdateTime: now,
  56. })
  57. require.NoError(t, err)
  58. urId, _ := urRes.LastInsertId()
  59. pmRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  60. ProductCode: uid, Name: "perm1", Code: uid + "_perm",
  61. Status: 1, CreateTime: now, UpdateTime: now,
  62. })
  63. require.NoError(t, err)
  64. pmId, _ := pmRes.LastInsertId()
  65. upRes, err := svcCtx.SysUserPermModel.Insert(ctx, &userperm.SysUserPerm{
  66. UserId: uId, PermId: pmId, Effect: "ALLOW",
  67. CreateTime: now, UpdateTime: now,
  68. })
  69. require.NoError(t, err)
  70. upId, _ := upRes.LastInsertId()
  71. t.Cleanup(func() {
  72. testutil.CleanTable(ctx, conn, "`sys_user_perm`", upId)
  73. testutil.CleanTable(ctx, conn, "`sys_user_role`", urId)
  74. testutil.CleanTable(ctx, conn, "`sys_perm`", pmId)
  75. testutil.CleanTable(ctx, conn, "`sys_role`", rId)
  76. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  77. testutil.CleanTable(ctx, conn, "`sys_user`", uId)
  78. testutil.CleanTable(ctx, conn, "`sys_product`", pId)
  79. })
  80. logic := NewRemoveMemberLogic(ctx, svcCtx)
  81. err = logic.RemoveMember(&types.RemoveMemberReq{Id: mId})
  82. require.NoError(t, err)
  83. _, err = svcCtx.SysProductMemberModel.FindOne(ctx, mId)
  84. assert.Error(t, err)
  85. roles, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, uId)
  86. require.NoError(t, err)
  87. assert.Empty(t, roles)
  88. allow, err := svcCtx.SysUserPermModel.FindPermIdsByUserIdAndEffectForProduct(ctx, uId, "ALLOW", uid)
  89. require.NoError(t, err)
  90. assert.Empty(t, allow)
  91. deny, err := svcCtx.SysUserPermModel.FindPermIdsByUserIdAndEffectForProduct(ctx, uId, "DENY", uid)
  92. require.NoError(t, err)
  93. assert.Empty(t, deny)
  94. }
  95. // TC-0228: 成员不存在
  96. func TestRemoveMember_NotFound(t *testing.T) {
  97. ctx := ctxhelper.SuperAdminCtx()
  98. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  99. logic := NewRemoveMemberLogic(ctx, svcCtx)
  100. err := logic.RemoveMember(&types.RemoveMemberReq{Id: 999999999})
  101. require.Error(t, err)
  102. ce, ok := err.(*response.CodeError)
  103. require.True(t, ok)
  104. assert.Equal(t, 404, ce.Code())
  105. assert.Equal(t, "成员不存在", ce.Error())
  106. }
  107. // TC-0227: 跨产品隔离
  108. func TestRemoveMember_CrossProductIsolation(t *testing.T) {
  109. ctx := ctxhelper.SuperAdminCtx()
  110. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  111. conn := testutil.GetTestSqlConn()
  112. now := time.Now().Unix()
  113. uid1 := testutil.UniqueId()
  114. uid2 := testutil.UniqueId()
  115. p1Res, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  116. Code: uid1, Name: "prod1", AppKey: uid1, AppSecret: "s1",
  117. Status: 1, CreateTime: now, UpdateTime: now,
  118. })
  119. require.NoError(t, err)
  120. p1Id, _ := p1Res.LastInsertId()
  121. p2Res, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  122. Code: uid2, Name: "prod2", AppKey: uid2, AppSecret: "s2",
  123. Status: 1, CreateTime: now, UpdateTime: now,
  124. })
  125. require.NoError(t, err)
  126. p2Id, _ := p2Res.LastInsertId()
  127. uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  128. Username: uid1, Password: testutil.HashPassword("pass"), Nickname: "nick",
  129. Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
  130. Status: 1, CreateTime: now, UpdateTime: now,
  131. })
  132. require.NoError(t, err)
  133. uId, _ := uRes.LastInsertId()
  134. m1Res, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  135. ProductCode: uid1, UserId: uId, MemberType: "MEMBER",
  136. Status: 1, CreateTime: now, UpdateTime: now,
  137. })
  138. require.NoError(t, err)
  139. m1Id, _ := m1Res.LastInsertId()
  140. m2Res, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  141. ProductCode: uid2, UserId: uId, MemberType: "MEMBER",
  142. Status: 1, CreateTime: now, UpdateTime: now,
  143. })
  144. require.NoError(t, err)
  145. m2Id, _ := m2Res.LastInsertId()
  146. r1Res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  147. ProductCode: uid1, Name: uid1, Status: 1, PermsLevel: 1,
  148. CreateTime: now, UpdateTime: now,
  149. })
  150. require.NoError(t, err)
  151. r1Id, _ := r1Res.LastInsertId()
  152. r2Res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  153. ProductCode: uid2, Name: uid2, Status: 1, PermsLevel: 1,
  154. CreateTime: now, UpdateTime: now,
  155. })
  156. require.NoError(t, err)
  157. r2Id, _ := r2Res.LastInsertId()
  158. ur1Res, err := svcCtx.SysUserRoleModel.Insert(ctx, &userrole.SysUserRole{
  159. UserId: uId, RoleId: r1Id, CreateTime: now, UpdateTime: now,
  160. })
  161. require.NoError(t, err)
  162. ur1Id, _ := ur1Res.LastInsertId()
  163. ur2Res, err := svcCtx.SysUserRoleModel.Insert(ctx, &userrole.SysUserRole{
  164. UserId: uId, RoleId: r2Id, CreateTime: now, UpdateTime: now,
  165. })
  166. require.NoError(t, err)
  167. ur2Id, _ := ur2Res.LastInsertId()
  168. t.Cleanup(func() {
  169. testutil.CleanTable(ctx, conn, "`sys_user_role`", ur1Id, ur2Id)
  170. testutil.CleanTable(ctx, conn, "`sys_role`", r1Id, r2Id)
  171. testutil.CleanTable(ctx, conn, "`sys_product_member`", m1Id, m2Id)
  172. testutil.CleanTable(ctx, conn, "`sys_user`", uId)
  173. testutil.CleanTable(ctx, conn, "`sys_product`", p1Id, p2Id)
  174. })
  175. logic := NewRemoveMemberLogic(ctx, svcCtx)
  176. err = logic.RemoveMember(&types.RemoveMemberReq{Id: m1Id})
  177. require.NoError(t, err)
  178. _, err = svcCtx.SysProductMemberModel.FindOne(ctx, m1Id)
  179. assert.Error(t, err)
  180. m2, err := svcCtx.SysProductMemberModel.FindOne(ctx, m2Id)
  181. require.NoError(t, err)
  182. assert.Equal(t, uid2, m2.ProductCode)
  183. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, uId)
  184. require.NoError(t, err)
  185. assert.Contains(t, roleIds, r2Id)
  186. assert.NotContains(t, roleIds, r1Id)
  187. }
  188. // strPtr / int64Ptr 是 后 UpdateMemberReq.MemberType / Status 指针化的 helper。
  189. // 若 nil 表示不改该字段,两者都 nil 会被 Logic 400。
  190. func strPtr(s string) *string { return &s }
  191. type seededProduct struct {
  192. code string
  193. pId int64
  194. uId int64
  195. mId int64
  196. admin int64 // 成员 id 当该成员为 ADMIN
  197. }
  198. // seedEnabledProductWithMember 创建 enabled product + user + product_member(memberType 指定)
  199. func seedEnabledProductWithMember(t *testing.T, svcCtx *svc.ServiceContext, memberType string) seededProduct {
  200. t.Helper()
  201. ctx := ctxhelper.SuperAdminCtx()
  202. now := time.Now().Unix()
  203. code := testutil.UniqueId()
  204. pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  205. Code: code, Name: "p_" + code, AppKey: code + "_k", AppSecret: "s",
  206. Status: 1, CreateTime: now, UpdateTime: now,
  207. })
  208. require.NoError(t, err)
  209. pId, _ := pRes.LastInsertId()
  210. uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  211. Username: code, Password: testutil.HashPassword("pw"), Nickname: "n",
  212. Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
  213. Status: 1, CreateTime: now, UpdateTime: now,
  214. })
  215. require.NoError(t, err)
  216. uId, _ := uRes.LastInsertId()
  217. mRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  218. ProductCode: code, UserId: uId, MemberType: memberType,
  219. Status: 1, CreateTime: now, UpdateTime: now,
  220. })
  221. require.NoError(t, err)
  222. mId, _ := mRes.LastInsertId()
  223. return seededProduct{code: code, pId: pId, uId: uId, mId: mId, admin: mId}
  224. }
  225. func cleanupSeeded(t *testing.T, svcCtx *svc.ServiceContext, sp seededProduct) {
  226. t.Helper()
  227. ctx := ctxhelper.SuperAdminCtx()
  228. conn := testutil.GetTestSqlConn()
  229. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", sp.code)
  230. testutil.CleanTable(ctx, conn, "`sys_user`", sp.uId)
  231. testutil.CleanTable(ctx, conn, "`sys_product`", sp.pId)
  232. }
  233. // TC-0723: 修复:不能移除产品最后一个 ADMIN
  234. func TestRemoveMember_LastAdminRejected(t *testing.T) {
  235. ctx := ctxhelper.SuperAdminCtx()
  236. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  237. sp := seededProduct{code: testutil.UniqueId()}
  238. now := time.Now().Unix()
  239. pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  240. Code: sp.code, Name: "p_" + sp.code, AppKey: sp.code + "_k", AppSecret: "s",
  241. Status: 1, CreateTime: now, UpdateTime: now,
  242. })
  243. require.NoError(t, err)
  244. sp.pId, _ = pRes.LastInsertId()
  245. uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  246. Username: sp.code, Password: testutil.HashPassword("pw"),
  247. Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
  248. Status: 1, CreateTime: now, UpdateTime: now,
  249. })
  250. require.NoError(t, err)
  251. sp.uId, _ = uRes.LastInsertId()
  252. mRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  253. ProductCode: sp.code, UserId: sp.uId, MemberType: "ADMIN",
  254. Status: 1, CreateTime: now, UpdateTime: now,
  255. })
  256. require.NoError(t, err)
  257. sp.mId, _ = mRes.LastInsertId()
  258. t.Cleanup(func() { cleanupSeeded(t, svcCtx, sp) })
  259. logic := NewRemoveMemberLogic(ctx, svcCtx)
  260. err = logic.RemoveMember(&types.RemoveMemberReq{Id: sp.mId})
  261. require.Error(t, err)
  262. var ce *response.CodeError
  263. require.True(t, errors.As(err, &ce))
  264. assert.Equal(t, 400, ce.Code())
  265. assert.Contains(t, ce.Error(), "最后一个管理员")
  266. m, ferr := svcCtx.SysProductMemberModel.FindOne(ctx, sp.mId)
  267. require.NoError(t, ferr, "ADMIN 必须仍然存在")
  268. assert.Equal(t, "ADMIN", m.MemberType)
  269. }
  270. // TC-0724: 存在 >=2 个 ADMIN 时可以移除其中一个
  271. func TestRemoveMember_AdminNotLast_Allowed(t *testing.T) {
  272. ctx := ctxhelper.SuperAdminCtx()
  273. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  274. conn := testutil.GetTestSqlConn()
  275. now := time.Now().Unix()
  276. code := testutil.UniqueId()
  277. pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  278. Code: code, Name: "p_" + code, AppKey: code + "_k", AppSecret: "s",
  279. Status: 1, CreateTime: now, UpdateTime: now,
  280. })
  281. require.NoError(t, err)
  282. pId, _ := pRes.LastInsertId()
  283. var uIds, mIds []int64
  284. for i := 0; i < 2; i++ {
  285. uid := testutil.UniqueId() + "_a"
  286. uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  287. Username: uid, Password: testutil.HashPassword("pw"),
  288. Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
  289. Status: 1, CreateTime: now, UpdateTime: now,
  290. })
  291. require.NoError(t, err)
  292. uId, _ := uRes.LastInsertId()
  293. uIds = append(uIds, uId)
  294. mRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{
  295. ProductCode: code, UserId: uId, MemberType: "ADMIN",
  296. Status: 1, CreateTime: now, UpdateTime: now,
  297. })
  298. require.NoError(t, err)
  299. mId, _ := mRes.LastInsertId()
  300. mIds = append(mIds, mId)
  301. }
  302. t.Cleanup(func() {
  303. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  304. testutil.CleanTable(ctx, conn, "`sys_user`", uIds...)
  305. testutil.CleanTable(ctx, conn, "`sys_product`", pId)
  306. })
  307. err = NewRemoveMemberLogic(ctx, svcCtx).RemoveMember(&types.RemoveMemberReq{Id: mIds[0]})
  308. require.NoError(t, err)
  309. _, err = svcCtx.SysProductMemberModel.FindOne(ctx, mIds[0])
  310. require.Error(t, err)
  311. _, err = svcCtx.SysProductMemberModel.FindOne(ctx, mIds[1])
  312. require.NoError(t, err, "另一个 ADMIN 必须保留")
  313. }
  314. // TC-0728: 移除非 ADMIN 成员不受 last-admin 保护
  315. func TestRemoveMember_NonAdmin_Unaffected(t *testing.T) {
  316. ctx := ctxhelper.SuperAdminCtx()
  317. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  318. sp := seedEnabledProductWithMember(t, svcCtx, "MEMBER")
  319. t.Cleanup(func() { cleanupSeeded(t, svcCtx, sp) })
  320. err := NewRemoveMemberLogic(ctx, svcCtx).RemoveMember(&types.RemoveMemberReq{Id: sp.mId})
  321. require.NoError(t, err)
  322. }