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