文件
hyb-oa/server/tests/unit/services/reimbursementVisibility.test.js
T

108 行
4.3 KiB
JavaScript

const {
buildReadableReimbursementWhere,
canReadReimbursementBusinessFact,
} = require("../../../src/services/reimbursementVisibility");
const fs = require("node:fs");
const path = require("node:path");
const ACTOR_ID = "11111111-1111-4111-8111-111111111111";
const OTHER_ID = "22222222-2222-4222-8222-222222222222";
const REIMBURSEMENT_ID = "33333333-3333-4333-8333-333333333333";
describe("reimbursement visibility", () => {
it.each([
["global finance", ["finance"], {}],
["global finance manager", ["finance_mgr"], {}],
["global ceo", ["ceo"], {}],
["global hr", ["hr"], {}],
["global admin", ["admin"], {}],
[
"manager team",
["manager"],
{
OR: [
{ owner_id: ACTOR_ID },
{ submitter_id: ACTOR_ID },
{ owner: { direct_manager_id: ACTOR_ID } },
],
},
],
])("keeps the existing %s scope while adding the business-fact filter", (_label, roleCodes, expectedScope) => {
expect(buildReadableReimbursementWhere({
employee: { id: ACTOR_ID, roleCodes },
scope: "all",
filters: { status: "draft" },
})).toEqual({
AND: [
expectedScope,
{
OR: [
{ status: { not: "draft" } },
{ owner_id: ACTOR_ID },
{ submitter_id: ACTOR_ID },
{ workflow_instances: { some: {} } },
],
},
{ status: "draft" },
],
});
});
it("keeps own scope independent from workflow history", () => {
expect(buildReadableReimbursementWhere({
employee: { id: ACTOR_ID, roleCodes: ["employee"] },
scope: "own",
filters: { status: "draft" },
})).toEqual({
AND: [
{ OR: [{ owner_id: ACTOR_ID }, { submitter_id: ACTOR_ID }] },
{ status: "draft" },
],
});
});
it("keeps every reimbursement read surface on the shared visibility contract", () => {
const routesDir = path.resolve(__dirname, "../../../src/routes");
const reimbursementsSource = fs.readFileSync(path.join(routesDir, "reimbursements.js"), "utf8");
const searchSource = fs.readFileSync(path.join(routesDir, "search.js"), "utf8");
const exportsSource = fs.readFileSync(path.join(routesDir, "exports.js"), "utf8");
const dashboardSource = fs.readFileSync(path.join(routesDir, "dashboard.js"), "utf8");
expect(reimbursementsSource).toContain("await ensureOwnOrCanViewAll(fastify, request, reimbursement)");
expect(reimbursementsSource.match(/await ensureOwnCanViewAllOrWorkflowReviewer\(fastify, request/g)).toHaveLength(4);
expect(reimbursementsSource).toContain("buildReadableReimbursementWhere({");
expect(reimbursementsSource).toContain("EXISTS (");
expect(searchSource).toContain("buildReadableReimbursementWhere({");
expect(exportsSource.match(/buildReadableReimbursementWhere\(\{/g)).toHaveLength(2);
expect(dashboardSource).toContain("buildReadableReimbursementWhere({");
});
it.each([
["owner draft", { owner_id: ACTOR_ID, submitter_id: OTHER_ID, status: "draft" }, null, true],
["submitter draft", { owner_id: OTHER_ID, submitter_id: ACTOR_ID, status: "draft" }, null, true],
["other approved", { owner_id: OTHER_ID, submitter_id: OTHER_ID, status: "approved" }, null, true],
["other returned draft", { owner_id: OTHER_ID, submitter_id: OTHER_ID, status: "draft" }, { id: "returned" }, true],
["other withdrawn draft", { owner_id: OTHER_ID, submitter_id: OTHER_ID, status: "draft" }, { id: "cancelled" }, true],
["other never-submitted draft", { owner_id: OTHER_ID, submitter_id: OTHER_ID, status: "draft" }, null, false],
])("classifies %s", async (_label, record, workflowInstance, expected) => {
const prisma = {
reimbursementForm: { findUnique: vi.fn() },
workflowInstance: { findFirst: vi.fn().mockResolvedValue(workflowInstance) },
};
await expect(canReadReimbursementBusinessFact(prisma, {
id: REIMBURSEMENT_ID,
...record,
}, ACTOR_ID)).resolves.toBe(expected);
if (record.status === "draft" && record.owner_id !== ACTOR_ID && record.submitter_id !== ACTOR_ID) {
expect(prisma.workflowInstance.findFirst).toHaveBeenCalledWith({
where: { entity_type: "reimbursement", entity_id: REIMBURSEMENT_ID },
select: { id: true },
});
} else {
expect(prisma.workflowInstance.findFirst).not.toHaveBeenCalled();
}
});
});