739 lines
23 KiB
TypeScript
739 lines
23 KiB
TypeScript
import { SystemRole } from "@capakraken/shared";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { entitlementRouter } from "../router/entitlement.js";
|
|
import { createCallerFactory } from "../trpc.js";
|
|
|
|
// Mock @capakraken/db to provide the enums used in the router
|
|
vi.mock("@capakraken/db", () => ({
|
|
VacationType: { ANNUAL: "ANNUAL", SICK: "SICK", OTHER: "OTHER", PUBLIC_HOLIDAY: "PUBLIC_HOLIDAY" },
|
|
VacationStatus: { APPROVED: "APPROVED", PENDING: "PENDING", REJECTED: "REJECTED" },
|
|
}));
|
|
|
|
const createCaller = createCallerFactory(entitlementRouter);
|
|
|
|
// ── Caller factories ─────────────────────────────────────────────────────────
|
|
|
|
/** Injects a default resource ownership mock so the ownership check in getBalance passes */
|
|
function createProtectedCaller(db: Record<string, unknown>) {
|
|
const withResourceOwnership = {
|
|
resource: {
|
|
findUnique: vi.fn().mockImplementation(async (args?: { select?: Record<string, unknown> }) => {
|
|
const select = args?.select ?? {};
|
|
return {
|
|
...(select.userId ? { userId: "user_1" } : {}),
|
|
...(select.federalState ? { federalState: "BY" } : {}),
|
|
...(select.country ? { country: { code: "DE" } } : {}),
|
|
...(select.metroCity ? { metroCity: null } : {}),
|
|
};
|
|
}),
|
|
},
|
|
...db,
|
|
};
|
|
return createCaller({
|
|
session: {
|
|
user: { email: "user@example.com", name: "User", image: null },
|
|
expires: "2099-01-01T00:00:00.000Z",
|
|
},
|
|
db: withResourceOwnership as never,
|
|
dbUser: {
|
|
id: "user_1",
|
|
systemRole: SystemRole.USER,
|
|
permissionOverrides: null,
|
|
},
|
|
});
|
|
}
|
|
|
|
function createManagerCaller(db: Record<string, unknown>) {
|
|
return createCaller({
|
|
session: {
|
|
user: { email: "mgr@example.com", name: "Manager", image: null },
|
|
expires: "2099-01-01T00:00:00.000Z",
|
|
},
|
|
db: db as never,
|
|
dbUser: {
|
|
id: "user_mgr",
|
|
systemRole: SystemRole.MANAGER,
|
|
permissionOverrides: null,
|
|
},
|
|
});
|
|
}
|
|
|
|
function createAdminCaller(db: Record<string, unknown>) {
|
|
return createCaller({
|
|
session: {
|
|
user: { email: "admin@example.com", name: "Admin", image: null },
|
|
expires: "2099-01-01T00:00:00.000Z",
|
|
},
|
|
db: db as never,
|
|
dbUser: {
|
|
id: "user_admin",
|
|
systemRole: SystemRole.ADMIN,
|
|
permissionOverrides: null,
|
|
},
|
|
});
|
|
}
|
|
|
|
// ── Sample data ──────────────────────────────────────────────────────────────
|
|
|
|
function sampleEntitlement(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: "ent_1",
|
|
resourceId: "res_1",
|
|
year: 2026,
|
|
entitledDays: 30,
|
|
carryoverDays: 2,
|
|
usedDays: 5,
|
|
pendingDays: 3,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function mockEntitlementFindUniqueByYear(
|
|
entitlementsByYear: Record<number, ReturnType<typeof sampleEntitlement> | null>,
|
|
) {
|
|
return vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => (
|
|
entitlementsByYear[where.resourceId_year.year] ?? null
|
|
));
|
|
}
|
|
|
|
// ─── getBalance ──────────────────────────────────────────────────────────────
|
|
|
|
describe("entitlement.getBalance", () => {
|
|
it("returns vacation balance for a resource and year", async () => {
|
|
const entitlement = sampleEntitlement();
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockResolvedValue(entitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.year).toBe(2026);
|
|
expect(result.resourceId).toBe("res_1");
|
|
expect(result.entitledDays).toBe(30);
|
|
expect(result.remainingDays).toBe(22); // 30 - 5 - 3
|
|
expect(result).toHaveProperty("sickDays");
|
|
});
|
|
|
|
it("creates entitlement with carryover when none exists", async () => {
|
|
const prevEntitlement = sampleEntitlement({
|
|
id: "ent_prev",
|
|
year: 2025,
|
|
entitledDays: 28,
|
|
usedDays: 20,
|
|
pendingDays: 0,
|
|
});
|
|
const createdEntitlement = sampleEntitlement({
|
|
year: 2026,
|
|
entitledDays: 36, // 28 default + 8 carryover
|
|
carryoverDays: 8,
|
|
usedDays: 0,
|
|
pendingDays: 0,
|
|
});
|
|
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({
|
|
2025: prevEntitlement,
|
|
}),
|
|
create: vi.fn().mockResolvedValue(createdEntitlement),
|
|
update: vi.fn().mockResolvedValue(createdEntitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.entitledDays).toBe(36);
|
|
expect(result.carryoverDays).toBe(8);
|
|
expect(db.vacationEntitlement.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
resourceId: "res_1",
|
|
year: 2026,
|
|
carryoverDays: 8,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("uses default of 28 days when no system settings exist", async () => {
|
|
const entitlement = sampleEntitlement({ entitledDays: 28, carryoverDays: 0 });
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockResolvedValue(entitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.entitledDays).toBe(28);
|
|
});
|
|
|
|
it("counts sick days separately", async () => {
|
|
const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0 });
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockResolvedValue(entitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi
|
|
.fn()
|
|
// Public holiday vacations for holiday context
|
|
.mockResolvedValueOnce([])
|
|
// First call: balance-type vacations (for syncEntitlement)
|
|
.mockResolvedValueOnce([])
|
|
// Second call: sick days
|
|
.mockResolvedValueOnce([
|
|
{
|
|
startDate: new Date("2026-03-10"),
|
|
endDate: new Date("2026-03-12"),
|
|
isHalfDay: false,
|
|
},
|
|
]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.sickDays).toBe(3);
|
|
});
|
|
|
|
it("does not deduct city-specific public holidays from leave balance", async () => {
|
|
const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0, entitledDays: 30, carryoverDays: 0 });
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
},
|
|
resource: {
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
userId: "user_1",
|
|
federalState: "BY",
|
|
country: { code: "DE" },
|
|
metroCity: { name: "Augsburg" },
|
|
}),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2028: entitlement }),
|
|
update: vi.fn().mockImplementation(async ({ data }) => ({
|
|
...entitlement,
|
|
...data,
|
|
})),
|
|
},
|
|
vacation: {
|
|
findMany: vi
|
|
.fn()
|
|
.mockResolvedValueOnce([])
|
|
.mockResolvedValueOnce([
|
|
{
|
|
startDate: new Date("2028-08-08T00:00:00.000Z"),
|
|
endDate: new Date("2028-08-08T00:00:00.000Z"),
|
|
status: "APPROVED",
|
|
isHalfDay: false,
|
|
},
|
|
])
|
|
.mockResolvedValueOnce([])
|
|
.mockResolvedValueOnce([]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2028 });
|
|
|
|
expect(result.usedDays).toBe(0);
|
|
expect(result.remainingDays).toBe(30);
|
|
});
|
|
|
|
it("recomputes carryover from the previous year when the next year already exists", async () => {
|
|
const entitlements = new Map([
|
|
[2025, sampleEntitlement({
|
|
id: "ent_2025",
|
|
year: 2025,
|
|
entitledDays: 28,
|
|
carryoverDays: 0,
|
|
usedDays: 8,
|
|
pendingDays: 0,
|
|
})],
|
|
[2026, sampleEntitlement({
|
|
id: "ent_2026",
|
|
year: 2026,
|
|
entitledDays: 28,
|
|
carryoverDays: 0,
|
|
usedDays: 0,
|
|
pendingDays: 0,
|
|
})],
|
|
]);
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
resource: {
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
userId: "user_1",
|
|
federalState: "BY",
|
|
countryId: "country_de",
|
|
metroCityId: null,
|
|
country: { code: "DE" },
|
|
metroCity: null,
|
|
}),
|
|
},
|
|
holidayCalendar: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => (
|
|
entitlements.get(where.resourceId_year.year) ?? null
|
|
)),
|
|
create: vi.fn(),
|
|
update: vi.fn().mockImplementation(async ({ where, data }: {
|
|
where: { id: string };
|
|
data: Record<string, number>;
|
|
}) => {
|
|
const current = [...entitlements.values()].find((entry) => entry.id === where.id);
|
|
if (!current) {
|
|
throw new Error(`Unknown entitlement ${where.id}`);
|
|
}
|
|
const updated = { ...current, ...data };
|
|
entitlements.set(updated.year, updated);
|
|
return updated;
|
|
}),
|
|
},
|
|
vacation: {
|
|
findMany: vi
|
|
.fn()
|
|
// 2025 holiday context
|
|
.mockResolvedValueOnce([])
|
|
// 2025 balance vacations
|
|
.mockResolvedValueOnce([
|
|
{
|
|
startDate: new Date("2025-06-10T00:00:00.000Z"),
|
|
endDate: new Date("2025-06-17T00:00:00.000Z"),
|
|
status: "APPROVED",
|
|
isHalfDay: false,
|
|
},
|
|
])
|
|
// 2026 holiday context
|
|
.mockResolvedValueOnce([])
|
|
// 2026 balance vacations
|
|
.mockResolvedValueOnce([])
|
|
// 2026 sick days
|
|
.mockResolvedValueOnce([]),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.carryoverDays).toBe(20);
|
|
expect(result.entitledDays).toBe(48);
|
|
expect(db.vacationEntitlement.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: { id: "ent_2026" },
|
|
data: expect.objectContaining({
|
|
carryoverDays: 20,
|
|
entitledDays: 48,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("entitlement.getBalanceDetail", () => {
|
|
it("returns assistant-friendly balance detail from the canonical balance workflow", async () => {
|
|
const entitlement = sampleEntitlement({ carryoverDays: 0, usedDays: 1, pendingDays: 0.5 });
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
resource: {
|
|
findUnique: vi.fn().mockImplementation(async ({ select }: { select?: Record<string, unknown> } = {}) => ({
|
|
...(select?.userId ? { userId: "user_1" } : {}),
|
|
...(select?.federalState ? { federalState: "BY" } : {}),
|
|
...(select?.country ? { country: { code: "DE" } } : {}),
|
|
...(select?.metroCity ? { metroCity: null } : {}),
|
|
...(select?.displayName ? { displayName: "Alice Example" } : {}),
|
|
...(select?.eid ? { eid: "EMP-001" } : {}),
|
|
})),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockResolvedValue(entitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockImplementation(async ({ where }: { where?: { type?: string } } = {}) => {
|
|
if (where?.type === "SICK") {
|
|
return [{ startDate: new Date("2026-02-01"), endDate: new Date("2026-02-01"), isHalfDay: false }];
|
|
}
|
|
return [];
|
|
}),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
const result = await caller.getBalanceDetail({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result).toEqual({
|
|
resource: "Alice Example",
|
|
eid: "EMP-001",
|
|
year: 2026,
|
|
entitlement: 30,
|
|
carryOver: 0,
|
|
taken: 1,
|
|
pending: 0.5,
|
|
remaining: 28.5,
|
|
sickDays: 1,
|
|
});
|
|
});
|
|
});
|
|
|
|
// ─── get ─────────────────────────────────────────────────────────────────────
|
|
|
|
describe("entitlement.get", () => {
|
|
it("returns existing entitlement (manager role)", async () => {
|
|
const entitlement = sampleEntitlement({
|
|
entitledDays: 30,
|
|
carryoverDays: 0,
|
|
usedDays: 0,
|
|
pendingDays: 0,
|
|
});
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 30 }),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockImplementation(async ({ data }: { data: Record<string, number> }) => ({
|
|
...entitlement,
|
|
...data,
|
|
})),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
const result = await caller.get({ resourceId: "res_1", year: 2026 });
|
|
|
|
expect(result.id).toBe("ent_1");
|
|
expect(result.entitledDays).toBe(30);
|
|
});
|
|
|
|
it("rejects access by a regular user (FORBIDDEN)", async () => {
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
};
|
|
|
|
const caller = createProtectedCaller(db);
|
|
await expect(caller.get({ resourceId: "res_1", year: 2026 })).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
// ─── set ─────────────────────────────────────────────────────────────────────
|
|
|
|
describe("entitlement.set", () => {
|
|
it("updates existing entitlement", async () => {
|
|
const existing = sampleEntitlement();
|
|
const updated = { ...existing, entitledDays: 35 };
|
|
const db = {
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn().mockResolvedValue(existing),
|
|
update: vi.fn().mockResolvedValue(updated),
|
|
create: vi.fn(),
|
|
},
|
|
auditLog: { create: vi.fn().mockResolvedValue({}) },
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
const result = await caller.set({
|
|
resourceId: "res_1",
|
|
year: 2026,
|
|
entitledDays: 35,
|
|
});
|
|
|
|
expect(result.entitledDays).toBe(35);
|
|
expect(db.vacationEntitlement.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: { id: "ent_1" },
|
|
data: { entitledDays: 35 },
|
|
}),
|
|
);
|
|
expect(db.vacationEntitlement.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("creates new entitlement when none exists", async () => {
|
|
const created = sampleEntitlement({ entitledDays: 30, carryoverDays: 0 });
|
|
const db = {
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
update: vi.fn(),
|
|
create: vi.fn().mockResolvedValue(created),
|
|
},
|
|
auditLog: { create: vi.fn().mockResolvedValue({}) },
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
const result = await caller.set({
|
|
resourceId: "res_1",
|
|
year: 2026,
|
|
entitledDays: 30,
|
|
});
|
|
|
|
expect(result.entitledDays).toBe(30);
|
|
expect(db.vacationEntitlement.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
resourceId: "res_1",
|
|
year: 2026,
|
|
entitledDays: 30,
|
|
carryoverDays: 0,
|
|
usedDays: 0,
|
|
pendingDays: 0,
|
|
}),
|
|
}),
|
|
);
|
|
expect(db.vacationEntitlement.update).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
// ─── bulkSet ─────────────────────────────────────────────────────────────────
|
|
|
|
describe("entitlement.bulkSet", () => {
|
|
it("upserts entitlements for all active resources (admin role)", async () => {
|
|
const resources = [{ id: "res_1" }, { id: "res_2" }, { id: "res_3" }];
|
|
const db = {
|
|
resource: {
|
|
findMany: vi.fn().mockResolvedValue(resources),
|
|
},
|
|
vacationEntitlement: {
|
|
upsert: vi.fn().mockResolvedValue(sampleEntitlement()),
|
|
},
|
|
auditLog: { create: vi.fn().mockResolvedValue({}) },
|
|
};
|
|
|
|
const caller = createAdminCaller(db);
|
|
const result = await caller.bulkSet({
|
|
year: 2026,
|
|
entitledDays: 30,
|
|
});
|
|
|
|
expect(result.updated).toBe(3);
|
|
expect(db.vacationEntitlement.upsert).toHaveBeenCalledTimes(3);
|
|
expect(db.resource.findMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({ isActive: true }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("filters by resourceIds when provided", async () => {
|
|
const resources = [{ id: "res_1" }];
|
|
const db = {
|
|
resource: {
|
|
findMany: vi.fn().mockResolvedValue(resources),
|
|
},
|
|
vacationEntitlement: {
|
|
upsert: vi.fn().mockResolvedValue(sampleEntitlement()),
|
|
},
|
|
auditLog: { create: vi.fn().mockResolvedValue({}) },
|
|
};
|
|
|
|
const caller = createAdminCaller(db);
|
|
await caller.bulkSet({
|
|
year: 2026,
|
|
entitledDays: 30,
|
|
resourceIds: ["res_1"],
|
|
});
|
|
|
|
expect(db.resource.findMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
isActive: true,
|
|
id: { in: ["res_1"] },
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("rejects bulk set by a manager (admin only)", async () => {
|
|
const db = {
|
|
resource: { findMany: vi.fn() },
|
|
vacationEntitlement: { upsert: vi.fn() },
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
await expect(
|
|
caller.bulkSet({ year: 2026, entitledDays: 30 }),
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
// ─── getYearSummary ──────────────────────────────────────────────────────────
|
|
|
|
describe("entitlement.getYearSummary", () => {
|
|
it("returns summary for all active resources (manager role)", async () => {
|
|
const resources = [
|
|
{ id: "res_1", displayName: "Alice", eid: "alice", chapter: "VFX" },
|
|
{ id: "res_2", displayName: "Bob", eid: "bob", chapter: "Animation" },
|
|
];
|
|
const entitlement = sampleEntitlement({ usedDays: 5, pendingDays: 2 });
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
resource: {
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
federalState: "BY",
|
|
country: { code: "DE" },
|
|
metroCity: null,
|
|
}),
|
|
findMany: vi.fn().mockResolvedValue(resources),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
|
|
update: vi.fn().mockResolvedValue(entitlement),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
const result = await caller.getYearSummary({ year: 2026 });
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0]).toHaveProperty("resourceId");
|
|
expect(result[0]).toHaveProperty("displayName");
|
|
expect(result[0]).toHaveProperty("remainingDays");
|
|
});
|
|
|
|
it("filters by chapter when provided", async () => {
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
},
|
|
resource: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn(),
|
|
update: vi.fn(),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn(),
|
|
},
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
await caller.getYearSummary({ year: 2026, chapter: "VFX" });
|
|
|
|
expect(db.resource.findMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
isActive: true,
|
|
chapter: "VFX",
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("entitlement.getYearSummaryDetail", () => {
|
|
it("returns assistant-friendly year summary detail from the canonical summary workflow", async () => {
|
|
const resources = [
|
|
{ id: "res_1", displayName: "Alice Example", eid: "EMP-001", chapter: "Delivery" },
|
|
{ id: "res_2", displayName: "Bob Example", eid: "EMP-002", chapter: "CGI" },
|
|
];
|
|
const db = {
|
|
systemSettings: {
|
|
findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
|
|
},
|
|
resource: {
|
|
findMany: vi.fn().mockResolvedValue(resources),
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
federalState: "BY",
|
|
country: { code: "DE" },
|
|
metroCity: null,
|
|
}),
|
|
},
|
|
vacationEntitlement: {
|
|
findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { resourceId: string; year: number } } }) => {
|
|
if (where.resourceId_year.year !== 2026) {
|
|
return null;
|
|
}
|
|
return sampleEntitlement({
|
|
id: `ent_${where.resourceId_year.resourceId}`,
|
|
resourceId: where.resourceId_year.resourceId,
|
|
year: 2026,
|
|
entitledDays: 28,
|
|
carryoverDays: 0,
|
|
usedDays: 0,
|
|
pendingDays: 0,
|
|
});
|
|
}),
|
|
create: vi.fn(),
|
|
update: vi.fn().mockImplementation(async (args?: { data?: Record<string, unknown>; where?: { id?: string } }) => ({
|
|
...sampleEntitlement({ entitledDays: 28, carryoverDays: 0, usedDays: 0, pendingDays: 0 }),
|
|
id: args?.where?.id ?? "ent_updated",
|
|
...(args?.data ?? {}),
|
|
})),
|
|
},
|
|
vacation: {
|
|
findMany: vi.fn().mockResolvedValue([]),
|
|
},
|
|
};
|
|
|
|
const caller = createManagerCaller(db);
|
|
const result = await caller.getYearSummaryDetail({ year: 2026, resourceName: "alice" });
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
resource: "Alice Example",
|
|
eid: "EMP-001",
|
|
chapter: "Delivery",
|
|
year: 2026,
|
|
entitled: 28,
|
|
carryover: 0,
|
|
used: 0,
|
|
pending: 0,
|
|
remaining: 28,
|
|
},
|
|
]);
|
|
});
|
|
});
|