checkManageAccessDeptZero_audit_test.go 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. package auth
  2. import (
  3. "context"
  4. "os"
  5. "testing"
  6. "perms-system-server/internal/consts"
  7. "perms-system-server/internal/loaders"
  8. "perms-system-server/internal/middleware"
  9. "perms-system-server/internal/response"
  10. "perms-system-server/internal/svc"
  11. "perms-system-server/internal/testutil"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. // ---------------------------------------------------------------------------
  16. // 审计 L-3(第 8 轮仍未落地)—— checkDeptHierarchy 对 caller.DeptId=0 / DeptPath=""
  17. // 的历史 MEMBER / DEVELOPER 账号直接 403。
  18. //
  19. // 契约期望(fix 后):历史账号任意一次管理动作时,CheckManageAccess 要么走
  20. // (a) 明确的"未归属部门,拒绝管理他人"403(当前行为,方向正确但文案 / 审计缺失)
  21. // (b) 自动把缺失部门挪到默认部门 → 正常走部门链校验
  22. // 无论走 (a) 还是 (b),都需要有 **response.CodeError 结构** 而不是普通 string error,
  23. // 否则前端做不到"按错误码触发数据迁移工单"。
  24. //
  25. // 本测试用 skipPending 标签,方便 report 识别未落地审计项;fix 落地(或数据迁移脚本
  26. // 跑完)后把 AUDIT_RUN_PENDING=1 打开并调整断言即可切换成真正的回归保护。
  27. // ---------------------------------------------------------------------------
  28. const auditPendingEnv = "AUDIT_RUN_PENDING"
  29. func skipPending(t *testing.T, marker, reason string) {
  30. t.Helper()
  31. if os.Getenv(auditPendingEnv) != "" {
  32. return
  33. }
  34. t.Skipf("AUDIT_PENDING %s (Round 8 fix 未落地) —— %s", marker, reason)
  35. }
  36. // TC-0993: 历史 DEVELOPER(DeptId=0)对合法目标的管理操作 —— fix 后必须是
  37. // 可识别的 response.CodeError,且带有迁移提示("您未归属任何部门"),让运维据此跑数据迁移。
  38. func TestCheckManageAccess_L3_LegacyDeveloperWithDeptZero_MustReturnCodedError(t *testing.T) {
  39. skipPending(t, "L-3",
  40. "当前返回 403 但文案分叉('您未归属任何部门' / '您的部门信息异常'),审计建议"+
  41. "合一为 '您未归属任何部门' 且带 CodeError.Code=403;fix 落地后移除 Skip")
  42. ctx := context.Background()
  43. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  44. // caller 是 legacy developer,DeptId=0 / DeptPath=""。
  45. callerCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{
  46. UserId: 999001, Username: "legacy_dev", IsSuperAdmin: false,
  47. MemberType: consts.MemberTypeDeveloper, Status: consts.StatusEnabled,
  48. ProductCode: "test_product",
  49. // DeptId=0, DeptPath="" —— legacy 账号
  50. })
  51. err := CheckManageAccess(callerCtx, svcCtx, 999002 /* target */, "test_product")
  52. require.Error(t, err, "L-3:legacy caller 必须被拒绝")
  53. var ce *response.CodeError
  54. require.ErrorAs(t, err, &ce, "L-3:必须是 response.CodeError,不得为裸 error(前端无法据此触发迁移)")
  55. assert.Equal(t, 403, ce.Code(), "L-3:必须是 403 以便前端分类")
  56. assert.Contains(t, ce.Error(), "未归属",
  57. "L-3:文案必须显式提示'未归属任何部门',便于人工判定是否需要跑数据迁移")
  58. }