修复非参与人报销只读边界(08-28-reimbursement-nonparticipant-readonly-projection)
这个提交包含在:
@@ -44,7 +44,13 @@ function buildMultipartRequest({ files, fields = {} }) {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp({ reimbursementStatus = "draft", materialPreviewStore = null } = {}) {
|
||||
async function buildApp({
|
||||
reimbursementStatus = "draft",
|
||||
materialPreviewStore = null,
|
||||
employeeId = ACTOR_ID,
|
||||
roleCodes = ["employee"],
|
||||
permissionCodes = ["expense.edit_own", "expense.view_own"],
|
||||
} = {}) {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "hyb-oa-r111-reimbursement-attachments-"));
|
||||
const reimbursementId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
|
||||
const createdAttachments = [];
|
||||
@@ -160,9 +166,9 @@ async function buildApp({ reimbursementStatus = "draft", materialPreviewStore =
|
||||
throw new AppError({ code: "AUTH_001", message: "未登录", statusCode: 401 });
|
||||
}
|
||||
request.employee = {
|
||||
id: ACTOR_ID,
|
||||
roleCodes: ["employee"],
|
||||
permissionCodes: ["expense.edit_own", "expense.view_own"],
|
||||
id: employeeId,
|
||||
roleCodes,
|
||||
permissionCodes,
|
||||
};
|
||||
});
|
||||
app.decorate("hasPermission", (employee, permissionCode) => {
|
||||
@@ -202,6 +208,38 @@ async function buildApp({ reimbursementStatus = "draft", materialPreviewStore =
|
||||
}
|
||||
|
||||
describe("reimbursement attachment routes", () => {
|
||||
it("rejects an admin view-all nonparticipant before attachment side effects", async () => {
|
||||
const { app, prisma, tempDir, reimbursementId, createdAttachments } = await buildApp({
|
||||
employeeId: "finance-viewer",
|
||||
roleCodes: ["admin"],
|
||||
permissionCodes: ["expense.edit_own", "expense.view_all"],
|
||||
});
|
||||
|
||||
try {
|
||||
const requestPayload = buildMultipartRequest({
|
||||
files: [{
|
||||
filename: "synthetic.pdf",
|
||||
contentType: "application/pdf",
|
||||
content: "%PDF synthetic",
|
||||
}],
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/reimbursements/${reimbursementId}/attachments`,
|
||||
headers: { authorization: "Bearer test", ...requestPayload.headers },
|
||||
payload: requestPayload.payload,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
expect(createdAttachments).toHaveLength(0);
|
||||
expect(prisma.reimbursementAttachment.create).not.toHaveBeenCalled();
|
||||
expect(prisma.auditLog.create).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await app.close();
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("uploads five attachment materials with the production global one-file baseline", async () => {
|
||||
const { app, prisma, tempDir, reimbursementId, createdAttachments } = await buildApp();
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户