完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -0,0 +1,145 @@
|
||||
const Fastify = require("fastify");
|
||||
|
||||
const approvalRulesRoutes = require("../../../src/routes/approvalRules");
|
||||
const { AppError } = require("../../../src/utils/AppError");
|
||||
|
||||
const ACTOR_ID = "11111111-1111-4111-8111-111111111111";
|
||||
const APPROVAL_RULE_ID = "22222222-2222-4222-8222-222222222222";
|
||||
|
||||
function buildCreatePayload(roleCode) {
|
||||
return {
|
||||
name: `合成规则-${roleCode}`,
|
||||
minAmount: "0.00",
|
||||
maxAmount: "1000.00",
|
||||
level1Role: roleCode,
|
||||
level2Role: null,
|
||||
level3Role: null,
|
||||
ownerSelfFallbackRole: "finance_mgr",
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp() {
|
||||
const app = Fastify();
|
||||
const approvalRuleCreate = vi.fn(async ({ data }) => ({
|
||||
id: APPROVAL_RULE_ID,
|
||||
name: data.name,
|
||||
min_amount: data.min_amount,
|
||||
max_amount: data.max_amount,
|
||||
level1_role: data.level1_role,
|
||||
level2_role: data.level2_role,
|
||||
level3_role: data.level3_role,
|
||||
owner_self_fallback_role: data.owner_self_fallback_role,
|
||||
enabled: data.enabled,
|
||||
priority: data.priority,
|
||||
created_at: new Date("2026-07-18T00:00:00.000Z"),
|
||||
updated_at: new Date("2026-07-18T00:00:00.000Z"),
|
||||
}));
|
||||
const transactionContext = {
|
||||
approvalRule: {
|
||||
aggregate: vi.fn().mockResolvedValue({ _max: { priority: 0 } }),
|
||||
create: approvalRuleCreate,
|
||||
},
|
||||
auditLog: {
|
||||
create: vi.fn().mockResolvedValue({ id: "audit-1" }),
|
||||
},
|
||||
};
|
||||
const transaction = vi.fn(async (callback) => callback(transactionContext));
|
||||
|
||||
app.decorate("requirePermission", () => async (request) => {
|
||||
request.employee = {
|
||||
id: ACTOR_ID,
|
||||
permissionCodes: ["system.settings"],
|
||||
};
|
||||
});
|
||||
app.decorate("prisma", {
|
||||
$transaction: transaction,
|
||||
});
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
if (error instanceof AppError) {
|
||||
return reply.status(error.statusCode).send({
|
||||
requestId: request.id,
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
details: error.details ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
return reply.status(500).send({
|
||||
requestId: request.id,
|
||||
error: {
|
||||
code: "SYS_001",
|
||||
message: "服务器内部错误",
|
||||
details: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(approvalRulesRoutes, { prefix: "/api" });
|
||||
return { app, approvalRuleCreate, transaction };
|
||||
}
|
||||
|
||||
describe("reimbursement approval-rule role whitelist", () => {
|
||||
it.each(["manager", "ceo", "finance", "finance_mgr"])(
|
||||
"allows role %s because it carries expense.approve",
|
||||
async (roleCode) => {
|
||||
const testApp = await buildApp();
|
||||
try {
|
||||
const response = await testApp.app.inject({
|
||||
method: "POST",
|
||||
url: "/api/approval-rules",
|
||||
payload: buildCreatePayload(roleCode),
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.level1Role).toBe(roleCode);
|
||||
expect(testApp.approvalRuleCreate).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
await testApp.app.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["employee", "hr", "administration", "admin", "system_admin"])(
|
||||
"rejects role %s before a reimbursement approval rule is saved",
|
||||
async (roleCode) => {
|
||||
const testApp = await buildApp();
|
||||
try {
|
||||
const response = await testApp.app.inject({
|
||||
method: "POST",
|
||||
url: "/api/approval-rules",
|
||||
payload: buildCreatePayload(roleCode),
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error.code).toBe("VALID_001");
|
||||
expect(testApp.transaction).not.toHaveBeenCalled();
|
||||
expect(testApp.approvalRuleCreate).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await testApp.app.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["level1Role", "level2Role", "level3Role"])(
|
||||
"applies the reimbursement role whitelist to %s on update",
|
||||
async (fieldName) => {
|
||||
const testApp = await buildApp();
|
||||
try {
|
||||
const response = await testApp.app.inject({
|
||||
method: "PATCH",
|
||||
url: `/api/approval-rules/${APPROVAL_RULE_ID}`,
|
||||
payload: { [fieldName]: "employee" },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error.code).toBe("VALID_001");
|
||||
expect(testApp.transaction).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await testApp.app.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户