完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -131,9 +131,15 @@ async function buildApp({
|
||||
},
|
||||
};
|
||||
});
|
||||
const attachmentDelete = vi.fn(async ({ where }) => {
|
||||
if (state.attachment?.id === where.id) {
|
||||
state.attachment = null;
|
||||
}
|
||||
return { id: where.id };
|
||||
});
|
||||
|
||||
const tx = {
|
||||
purchaseAttachment: { create: attachmentCreate },
|
||||
purchaseAttachment: { create: attachmentCreate, delete: attachmentDelete },
|
||||
auditLog: { create: uploadAuditCreate },
|
||||
};
|
||||
const prisma = {
|
||||
@@ -211,6 +217,7 @@ async function buildApp({
|
||||
return {
|
||||
app,
|
||||
attachmentCreate,
|
||||
attachmentDelete,
|
||||
downloadAuditCreate,
|
||||
prisma,
|
||||
state,
|
||||
@@ -470,6 +477,58 @@ describe("purchase attachment upload", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("purchase attachment delete", () => {
|
||||
it("requires a reason and records it with the attachment audit", async () => {
|
||||
const { app, attachmentDelete, state, uploadAuditCreate } = await buildApp();
|
||||
|
||||
try {
|
||||
const response = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/api/purchase-requests/${PURCHASE_REQUEST_ID}/attachments/${ATTACHMENT_ID}`,
|
||||
headers: { authorization: "Bearer unit-test", "content-type": "application/json" },
|
||||
payload: { reason: "重复上传" },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data).toEqual({ outcome: "deleted", attachmentId: ATTACHMENT_ID });
|
||||
expect(attachmentDelete).toHaveBeenCalledWith({ where: { id: ATTACHMENT_ID } });
|
||||
expect(uploadAuditCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
action: "PURCHASE_ATTACHMENT_DELETED",
|
||||
target_id: ATTACHMENT_ID,
|
||||
extra: expect.objectContaining({ purchaseRequestId: PURCHASE_REQUEST_ID, reason: "重复上传" }),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(state.attachment).toBeNull();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a missing reason before attachment lookup or deletion", async () => {
|
||||
const { app, attachmentDelete, prisma, uploadAuditCreate } = await buildApp();
|
||||
|
||||
try {
|
||||
const response = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/api/purchase-requests/${PURCHASE_REQUEST_ID}/attachments/${ATTACHMENT_ID}`,
|
||||
headers: { authorization: "Bearer unit-test", "content-type": "application/json" },
|
||||
payload: {},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error.code).toBe("VALID_001");
|
||||
expect(prisma.purchaseRequest.findUnique).not.toHaveBeenCalled();
|
||||
expect(attachmentDelete).not.toHaveBeenCalled();
|
||||
expect(uploadAuditCreate).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("purchase attachment authenticated download", () => {
|
||||
const downloadUrl = `/api/purchase-requests/${PURCHASE_REQUEST_ID}/attachments/${ATTACHMENT_ID}/download`;
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户