收紧报销草稿读取边界 [09-04-reimbursement-state-gap-finance-view][R2]

这个提交包含在:
2026-09-05 09:13:53 +08:00
父节点 1b120e0829
当前提交 8e0af8235e
共修改 14 个文件,包含 704 行新增和 120 行删除
@@ -208,6 +208,7 @@ async function buildApp(options = {}) {
id: reimbursement.id,
owner_id: reimbursement.owner_id,
submitter_id: reimbursement.submitter_id,
status: reimbursement.status,
form_number: reimbursement.form_number,
total_amount: reimbursement.total_amount,
owner: {
@@ -216,6 +217,9 @@ async function buildApp(options = {}) {
},
})),
},
workflowInstance: {
findFirst: vi.fn(async () => options.existingWorkflowInstance ?? null),
},
};
app.decorate("prisma", prisma);
@@ -718,7 +722,7 @@ describe("reimbursements routes expense standard integration", () => {
await app.close();
});
it("allows a view-all nonparticipant to read reimbursement precheck", async () => {
it("allows a view-all nonparticipant to read a submitted reimbursement precheck", async () => {
resolveEntityPrecheckMock.mockResolvedValue(buildPrecheck());
const { app } = await buildApp({
actor: {
@@ -727,6 +731,59 @@ describe("reimbursements routes expense standard integration", () => {
roleCodes: ["finance"],
permissionCodes: ["expense.submit", "expense.view_all"],
},
reimbursement: {
id: REIMBURSEMENT_ID,
owner_id: EMPLOYEE_ID,
submitter_id: EMPLOYEE_ID,
department_id: DEPARTMENT_ID,
reimbursement_date: new Date("2026-04-08T00:00:00.000Z"),
status: "approved",
form_number: "BX-20260408-001",
total_amount: "180.00",
},
});
const response = await app.inject({
method: "POST",
url: `/api/reimbursements/${REIMBURSEMENT_ID}/precheck`,
});
expect(response.statusCode).toBe(200);
expect(resolveEntityPrecheckMock).toHaveBeenCalledTimes(1);
await app.close();
});
it("rejects a view-all nonparticipant precheck for a never-submitted draft", async () => {
const { app } = await buildApp({
actor: {
id: "finance-viewer",
department_id: DEPARTMENT_ID,
roleCodes: ["finance"],
permissionCodes: ["expense.submit", "expense.view_all"],
},
});
const response = await app.inject({
method: "POST",
url: `/api/reimbursements/${REIMBURSEMENT_ID}/precheck`,
});
expect(response.statusCode).toBe(403);
expect(response.json().error).toMatchObject({ code: "PERM_001", message: "无权限" });
expect(resolveEntityPrecheckMock).not.toHaveBeenCalled();
await app.close();
});
it("allows a view-all nonparticipant precheck for a returned draft", async () => {
resolveEntityPrecheckMock.mockResolvedValue(buildPrecheck());
const { app } = await buildApp({
actor: {
id: "finance-viewer",
department_id: DEPARTMENT_ID,
roleCodes: ["finance"],
permissionCodes: ["expense.submit", "expense.view_all"],
},
existingWorkflowInstance: { id: "returned-workflow" },
});
const response = await app.inject({