roleDetailOracle_audit_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package role
  2. import (
  3. "errors"
  4. "testing"
  5. "time"
  6. permModel "perms-system-server/internal/model/perm"
  7. roleModel "perms-system-server/internal/model/role"
  8. "perms-system-server/internal/model/roleperm"
  9. "perms-system-server/internal/response"
  10. "perms-system-server/internal/svc"
  11. "perms-system-server/internal/testutil"
  12. "perms-system-server/internal/testutil/ctxhelper"
  13. "perms-system-server/internal/types"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/stretchr/testify/require"
  16. )
  17. // ---------------------------------------------------------------------------
  18. // 覆盖目标:审计 M-N3 修复 —— RoleDetail 必须消除"角色不存在 vs 跨产品访问"的枚举 oracle。
  19. // 旧实现:存在但在别的产品 → 403;不存在 → 404。遍历 id 即可画出跨产品 roleId 分布图。
  20. // 新契约:
  21. // - 非超管跨产品访问 → 404 "角色不存在"(与真正 NotFound 响应体完全一致)
  22. // - 超管仍可跨产品访问(审计/运维需要)
  23. // ---------------------------------------------------------------------------
  24. // TC-1000: M-N3 —— 非超管访问别的产品的 role 必须 404,与 "id 不存在" 响应体一致。
  25. func TestRoleDetail_MN3_CrossProductReturns404NotFound(t *testing.T) {
  26. ctx := ctxhelper.SuperAdminCtx()
  27. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  28. conn := testutil.GetTestSqlConn()
  29. now := time.Now().Unix()
  30. // 插入别的产品("test_product2")下的 role
  31. otherProduct := "mn3_other_" + testutil.UniqueId()
  32. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  33. ProductCode: otherProduct, Name: "mn3_role_" + testutil.UniqueId(),
  34. Remark: "mn3", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now,
  35. })
  36. require.NoError(t, err)
  37. roleId, _ := roleRes.LastInsertId()
  38. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
  39. // Admin 身份在 "test_product" 下访问 "mn3_other_*" 的 roleId
  40. adminCtx := ctxhelper.AdminCtx("test_product")
  41. resp, err := NewRoleDetailLogic(adminCtx, svcCtx).RoleDetail(&types.RoleDetailReq{Id: roleId})
  42. assert.Nil(t, resp)
  43. require.Error(t, err)
  44. var ce *response.CodeError
  45. require.True(t, errors.As(err, &ce))
  46. assert.Equal(t, 404, ce.Code(),
  47. "M-N3:跨产品访问必须 404,不得以 403 暴露存在性")
  48. assert.Equal(t, "角色不存在", ce.Error(),
  49. "M-N3:响应文案必须与 'id 真实不存在' 完全一致,彻底关闭枚举 oracle")
  50. }
  51. // TC-1001: M-N3 对照 —— "id 不存在" 的响应必须与 "跨产品访问" 完全一致(code 与 body)。
  52. func TestRoleDetail_MN3_NotFoundAndCrossProduct_Indistinguishable(t *testing.T) {
  53. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  54. conn := testutil.GetTestSqlConn()
  55. ctx := ctxhelper.SuperAdminCtx()
  56. now := time.Now().Unix()
  57. // 预埋一条别的产品的角色
  58. otherProduct := "mn3_cmp_" + testutil.UniqueId()
  59. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  60. ProductCode: otherProduct, Name: "mn3cmp_" + testutil.UniqueId(),
  61. Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now,
  62. })
  63. require.NoError(t, err)
  64. existingId, _ := roleRes.LastInsertId()
  65. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", existingId) })
  66. adminCtx := ctxhelper.AdminCtx("test_product")
  67. // (A) 跨产品访问真实存在的 id
  68. _, errCross := NewRoleDetailLogic(adminCtx, svcCtx).RoleDetail(&types.RoleDetailReq{Id: existingId})
  69. require.Error(t, errCross)
  70. var ceA *response.CodeError
  71. require.True(t, errors.As(errCross, &ceA))
  72. // (B) 访问一个肯定不存在的 id(id 选在 existingId + 很大偏移,规避数据库递增到该值)
  73. _, errAbsent := NewRoleDetailLogic(adminCtx, svcCtx).RoleDetail(
  74. &types.RoleDetailReq{Id: existingId + 999_999_999},
  75. )
  76. require.Error(t, errAbsent)
  77. var ceB *response.CodeError
  78. require.True(t, errors.As(errAbsent, &ceB))
  79. // 响应码与文案必须完全一致,不给任何侧信道
  80. assert.Equal(t, ceB.Code(), ceA.Code(),
  81. "M-N3:跨产品 vs id 不存在必须返回相同 code")
  82. assert.Equal(t, ceB.Error(), ceA.Error(),
  83. "M-N3:跨产品 vs id 不存在必须返回相同 body")
  84. }
  85. // TC-1002: M-N3 —— 超管跨产品仍然可以正常访问(审计/运维路径不能被误伤)。
  86. func TestRoleDetail_MN3_SuperAdminCanStillAccessCrossProduct(t *testing.T) {
  87. ctx := ctxhelper.SuperAdminCtx()
  88. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  89. conn := testutil.GetTestSqlConn()
  90. now := time.Now().Unix()
  91. otherProduct := "mn3_sa_" + testutil.UniqueId()
  92. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  93. ProductCode: otherProduct, Name: "mn3sa_" + testutil.UniqueId(),
  94. Remark: "sa_cross", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now,
  95. })
  96. require.NoError(t, err)
  97. roleId, _ := roleRes.LastInsertId()
  98. permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  99. ProductCode: otherProduct, Name: "mn3sa_p_" + testutil.UniqueId(), Code: testutil.UniqueId(),
  100. Status: 1, CreateTime: now, UpdateTime: now,
  101. })
  102. require.NoError(t, err)
  103. permId, _ := permRes.LastInsertId()
  104. rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &roleperm.SysRolePerm{
  105. RoleId: roleId, PermId: permId, CreateTime: now, UpdateTime: now,
  106. })
  107. require.NoError(t, err)
  108. rpId, _ := rpRes.LastInsertId()
  109. t.Cleanup(func() {
  110. testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpId)
  111. testutil.CleanTable(ctx, conn, "`sys_perm`", permId)
  112. testutil.CleanTable(ctx, conn, "`sys_role`", roleId)
  113. })
  114. // 超管在 "test_product" 当前身份,但跨产品访问 "mn3_sa_*" 的 role,应允许
  115. resp, err := NewRoleDetailLogic(ctx, svcCtx).RoleDetail(&types.RoleDetailReq{Id: roleId})
  116. require.NoError(t, err, "超管必须能跨产品查看 role,支撑审计/运维路径")
  117. require.NotNil(t, resp)
  118. assert.Equal(t, roleId, resp.Id)
  119. assert.Equal(t, otherProduct, resp.ProductCode)
  120. assert.Contains(t, resp.PermIds, permId)
  121. }