feat(api): admin account governance endpoints

P4a Task 6: GET /admin/users 双模式收口(无 accountPrefix 进入管理列表
模式:全量用户、query/status 过滤、createdAt+ID 倒序键集游标、仅 admin;
前缀模式 P1b 语义保留)+ PATCH / reset-password / activate / deactivate
治理端点(sourceStatus 只读拒绝、自我保护、会话撤销、审计、生成初始
密码)。附带修复 full-chain e2e 的 job-done 断言竞态(轮询条件补齐
photo_jobs 终态)。api e2e 214 → 227 全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 19:31:00 +08:00
parent 4937eeecff
commit f4a1ba7a14
9 changed files with 1326 additions and 60 deletions
+25 -8
View File
@@ -10,8 +10,8 @@
// 'carol' → 空、'Carol' → 命中;
// ④ LIKE 通配符转义(ESCAPE 子句):前缀中的 _ / % 按字面匹配,不作单字符/
// 任意串通配(esc_ 仅命中字面下划线账号;al% 空);
// ⑤ 参数边界:accountPrefix 缺失(过渡期实现仍 400,Task 6 起进入管理列表
// 模式)/1 字符/65 字符 → 400;2 与 64 字符合法;limit 缺省 20(P1b 时
// ⑤ 参数边界:accountPrefix 缺失 → 管理列表模式分流(staff 403 / admin
// 200,Task 6 双模式收口后语义)/1 字符/65 字符 → 400;2 与 64 字符合法;limit 缺省 20(P1b 时
// 为 10,P4a 统一分页 bounds)、1..100 界内、0/101/非整数 → 400;
// ⑥ 访问策略(P1b 决策,契约描述):未认证 401;任何已认证工作人员(含非
// admin 的 staff)200;admin 200。
@@ -197,13 +197,30 @@ describe('账号查询 GET /admin/users (e2e)', () => {
expect(percent.body.items).toEqual([]);
});
it('accountPrefix 边界:过渡期缺失/空串仍 400;1 字符/65 字符 → 400;64 字符 → 200 空', async () => {
// Task 6 起缺失 accountPrefix 进入管理列表模式(仅 admin);当前实现仅
// 前缀模式,缺失/空串仍 400(控制器文件头登记的过渡期边界)。
const missing = await lookup(staffToken, {});
expect(missing.status).toBe(400);
expect(missing.body.code).toBe(ErrorCodes.VALIDATION_FAILED);
it('accountPrefix 边界:缺失 → 双模式分流(staff 403 / admin 200 管理列表);1/65 字符 → 400;64 → 200 空', async () => {
// Task 6 起缺失 accountPrefix 进入管理列表模式(仅 admin):staff 无前缀
// → 403 AUTH_FORBIDDEN;admin → 200 管理列表(全量用户,含非激活态——
// 深度断言由 admin-users.e2e-spec 守护,此处只断言模式分流与响应形状)。
const staffMissing = await lookup(staffToken, {});
expect(staffMissing.status).toBe(403);
expect(staffMissing.body.code).toBe(ErrorCodes.AUTH_FORBIDDEN);
const adminMissing = await lookup(adminToken, {});
expect(adminMissing.status).toBe(200);
expect(Object.keys(adminMissing.body).sort()).toEqual([
'hasMore',
'items',
'nextCursor',
]);
// 管理列表包含前缀模式排除的非激活用户(本 spec 的排除组)。
const adminAccounts = (
adminMissing.body.items as { account: string }[]
).map((item) => item.account);
expect(adminAccounts).toContain('aldis');
expect(adminAccounts).toContain('alpend');
expect(adminAccounts).toContain('allock');
// 显式空串是「提供了非法值」而非「未提供」→ 仍 400。
const empty = await lookup(staffToken, { accountPrefix: '' });
expect(empty.status).toBe(400);
expect(empty.body.code).toBe(ErrorCodes.VALIDATION_FAILED);