Files
CapaKraken/packages/api/src/__tests__/holiday-calendar-router.test.ts
T

560 lines
16 KiB
TypeScript

import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { createCallerFactory } from "../trpc.js";
import { holidayCalendarRouter } from "../router/holiday-calendar.js";
vi.mock("../lib/audit.js", () => ({
createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));
const createCaller = createCallerFactory(holidayCalendarRouter);
function createProtectedCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "user@example.com", name: "User", image: null },
expires: "2026-12-31T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "user_1",
systemRole: SystemRole.USER,
permissionOverrides: null,
},
});
}
function createAdminCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "admin@example.com", name: "Admin", image: null },
expires: "2026-12-31T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "admin_1",
systemRole: SystemRole.ADMIN,
permissionOverrides: null,
},
});
}
describe("holiday calendar router", () => {
it("requires admin access for holiday calendar catalog reads", async () => {
const findMany = vi.fn();
const findUnique = vi.fn();
const findFirst = vi.fn();
const caller = createProtectedCaller({
holidayCalendar: {
findMany,
findUnique,
findFirst,
},
});
await expect(caller.listCalendars({ includeInactive: true })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.listCalendarsDetail({ includeInactive: true })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.getCalendarByIdentifier({ identifier: "Deutschland" })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.getCalendarByIdentifierDetail({ identifier: "Deutschland" })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.getCalendarById({ id: "cal_de" })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
expect(findMany).not.toHaveBeenCalled();
expect(findUnique).not.toHaveBeenCalled();
expect(findFirst).not.toHaveBeenCalled();
});
it("allows admins to read holiday calendar catalog routes", async () => {
const listRows = [
{
id: "cal_de",
name: "Deutschland",
scopeType: "COUNTRY",
stateCode: null,
isActive: true,
priority: 0,
country: { id: "country_de", code: "DE", name: "Deutschland" },
metroCity: null,
_count: { entries: 1 },
entries: [
{
id: "entry_1",
date: new Date("2026-01-01T00:00:00.000Z"),
name: "Neujahr",
isRecurringAnnual: true,
source: "builtin",
},
],
},
];
const detailRow = {
id: "cal_de",
name: "Deutschland",
scopeType: "COUNTRY",
stateCode: null,
isActive: true,
priority: 0,
country: { id: "country_de", code: "DE", name: "Deutschland" },
metroCity: null,
entries: [
{
id: "entry_1",
date: new Date("2026-01-01T00:00:00.000Z"),
name: "Neujahr",
isRecurringAnnual: true,
source: "builtin",
},
],
};
const findMany = vi.fn().mockResolvedValue(listRows);
const findUnique = vi
.fn()
.mockResolvedValueOnce(detailRow)
.mockResolvedValueOnce(detailRow)
.mockResolvedValueOnce(detailRow);
const findFirst = vi.fn();
const caller = createAdminCaller({
holidayCalendar: {
findMany,
findUnique,
findFirst,
},
});
const listResult = await caller.listCalendars({ includeInactive: true });
const detailResult = await caller.listCalendarsDetail({ includeInactive: true });
const byIdentifierResult = await caller.getCalendarByIdentifier({ identifier: "cal_de" });
const byIdentifierDetailResult = await caller.getCalendarByIdentifierDetail({ identifier: "cal_de" });
const byIdResult = await caller.getCalendarById({ id: "cal_de" });
expect(listResult).toEqual(listRows);
expect(detailResult).toEqual({
count: 1,
calendars: [
expect.objectContaining({
id: "cal_de",
entryCount: 1,
}),
],
});
expect(byIdentifierResult).toEqual(detailRow);
expect(byIdentifierDetailResult).toEqual(
expect.objectContaining({
id: "cal_de",
entryCount: 1,
}),
);
expect(byIdResult).toEqual(detailRow);
expect(findMany).toHaveBeenCalledTimes(2);
expect(findUnique).toHaveBeenCalledTimes(3);
expect(findFirst).not.toHaveBeenCalled();
});
it("lists holiday calendars with assistant-facing detail formatting", async () => {
const db = {
holidayCalendar: {
findMany: vi.fn().mockResolvedValue([
{
id: "cal_by",
name: "Bayern Feiertage",
scopeType: "STATE",
stateCode: "BY",
isActive: true,
priority: 10,
country: { id: "country_de", code: "DE", name: "Deutschland" },
metroCity: null,
_count: { entries: 2 },
entries: [
{
id: "entry_1",
date: new Date("2026-01-06T00:00:00.000Z"),
name: "Heilige Drei Koenige",
isRecurringAnnual: true,
source: "state",
},
],
},
]),
},
};
const caller = createAdminCaller(db);
const result = await caller.listCalendarsDetail({
countryCode: "DE",
scopeType: "STATE",
includeInactive: true,
});
expect(result).toEqual({
count: 1,
calendars: [
expect.objectContaining({
id: "cal_by",
name: "Bayern Feiertage",
scopeType: "STATE",
stateCode: "BY",
entryCount: 2,
country: { id: "country_de", code: "DE", name: "Deutschland" },
entries: [
expect.objectContaining({
id: "entry_1",
date: "2026-01-06",
name: "Heilige Drei Koenige",
isRecurringAnnual: true,
source: "state",
}),
],
}),
],
});
});
it("resolves a holiday calendar by identifier with assistant-facing detail formatting", async () => {
const db = {
holidayCalendar: {
findUnique: vi.fn().mockResolvedValue(null),
findFirst: vi
.fn()
.mockResolvedValueOnce({
id: "cal_augsburg",
name: "Augsburg lokal",
scopeType: "CITY",
stateCode: null,
isActive: true,
priority: 5,
country: { id: "country_de", code: "DE", name: "Deutschland" },
metroCity: { id: "city_augsburg", name: "Augsburg" },
entries: [
{
id: "entry_1",
date: new Date("2026-08-08T00:00:00.000Z"),
name: "Friedensfest lokal",
isRecurringAnnual: true,
source: "manual",
},
],
}),
},
};
const caller = createAdminCaller(db);
const result = await caller.getCalendarByIdentifierDetail({ identifier: "Augsburg lokal" });
expect(result).toEqual(
expect.objectContaining({
id: "cal_augsburg",
name: "Augsburg lokal",
scopeType: "CITY",
entryCount: 1,
metroCity: { id: "city_augsburg", name: "Augsburg" },
entries: [
expect.objectContaining({
id: "entry_1",
date: "2026-08-08",
name: "Friedensfest lokal",
}),
],
}),
);
});
it("merges built-in and scoped custom holidays in preview", async () => {
const db = {
country: {
findUnique: vi.fn().mockResolvedValue({ code: "DE" }),
},
metroCity: {
findUnique: vi.fn().mockResolvedValue({ name: "Augsburg" }),
},
holidayCalendar: {
findMany: vi.fn().mockResolvedValue([
{
id: "cal_city",
name: "Augsburg lokal",
scopeType: "CITY",
priority: 10,
createdAt: new Date("2026-01-01T00:00:00.000Z"),
entries: [
{
date: new Date("2020-01-01T00:00:00.000Z"),
name: "Augsburg Neujahr",
isRecurringAnnual: true,
},
{
date: new Date("2020-08-08T00:00:00.000Z"),
name: "Friedensfest lokal",
isRecurringAnnual: true,
},
],
},
]),
},
};
const caller = createProtectedCaller(db);
const result = await caller.previewResolvedHolidays({
countryId: "country_de",
metroCityId: "city_augsburg",
year: 2026,
});
expect(db.holidayCalendar.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
countryId: "country_de",
isActive: true,
}),
}),
);
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
date: "2026-01-01",
name: "Augsburg Neujahr",
scopeType: "CITY",
calendarName: "Augsburg lokal",
}),
expect.objectContaining({
date: "2026-08-08",
name: "Friedensfest lokal",
scopeType: "CITY",
calendarName: "Augsburg lokal",
}),
]),
);
});
it("formats preview results for assistant consumption", async () => {
const db = {
country: {
findUnique: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Deutschland" }),
},
metroCity: {
findUnique: vi.fn().mockResolvedValue({ id: "city_augsburg", name: "Augsburg", countryId: "country_de" }),
},
holidayCalendar: {
findMany: vi.fn().mockResolvedValue([
{
id: "cal_city",
name: "Augsburg lokal",
scopeType: "CITY",
priority: 10,
createdAt: new Date("2026-01-01T00:00:00.000Z"),
entries: [
{
id: "entry_1",
date: new Date("2020-08-08T00:00:00.000Z"),
name: "Friedensfest lokal",
isRecurringAnnual: true,
source: "manual",
},
],
},
]),
},
};
const caller = createProtectedCaller(db);
const result = await caller.previewResolvedHolidaysDetail({
countryId: "country_de",
metroCityId: "city_augsburg",
year: 2026,
});
expect(result.locationContext).toEqual({
countryId: "country_de",
countryCode: "DE",
stateCode: null,
metroCityId: "city_augsburg",
metroCity: "Augsburg",
year: 2026,
});
expect(result.summary.byScope).toEqual(
expect.arrayContaining([expect.objectContaining({ scope: "CITY" })]),
);
expect(result.holidays).toEqual(
expect.arrayContaining([
expect.objectContaining({
date: "2026-08-08",
name: "Friedensfest lokal",
scope: "CITY",
calendarName: "Augsburg lokal",
sourceType: "CUSTOM",
}),
]),
);
});
it("formats resolved holidays by region for assistant consumption", async () => {
const db = {
holidayCalendar: {
findMany: vi.fn().mockResolvedValue([]),
},
};
const caller = createProtectedCaller(db);
const result = await caller.resolveHolidaysDetail({
countryCode: "DE",
stateCode: "BY",
periodStart: new Date("2026-01-01T00:00:00.000Z"),
periodEnd: new Date("2026-12-31T00:00:00.000Z"),
});
expect(result.locationContext).toEqual({
countryId: null,
countryCode: "DE",
federalState: "BY",
metroCityId: null,
metroCity: null,
});
expect(result.count).toBeGreaterThan(0);
expect(result.summary.byScope).toEqual(
expect.arrayContaining([expect.objectContaining({ scope: "STATE" })]),
);
expect(result.holidays).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: "Heilige Drei Könige", date: "2026-01-06", scope: "STATE" }),
]),
);
});
it("formats resolved holidays for a resource including local city holidays", async () => {
const db = {
resource: {
findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
findUnique: vi.fn().mockResolvedValue({
id: "res_1",
eid: "bruce.banner",
displayName: "Bruce Banner",
federalState: "BY",
countryId: "country_de",
metroCityId: "city_augsburg",
country: { code: "DE", name: "Deutschland" },
metroCity: { name: "Augsburg" },
}),
},
holidayCalendar: {
findMany: vi.fn().mockResolvedValue([
{
id: "cal_city",
name: "Augsburg lokal",
scopeType: "CITY",
priority: 5,
createdAt: new Date("2026-01-01T00:00:00.000Z"),
entries: [
{
id: "entry_1",
date: new Date("2020-08-08T00:00:00.000Z"),
name: "Augsburger Friedensfest",
isRecurringAnnual: true,
source: "manual",
},
],
},
]),
},
};
const caller = createProtectedCaller(db);
const result = await caller.resolveResourceHolidaysDetail({
resourceId: "res_1",
periodStart: new Date("2026-01-01T00:00:00.000Z"),
periodEnd: new Date("2026-12-31T00:00:00.000Z"),
});
expect(result.resource).toEqual(
expect.objectContaining({
eid: "bruce.banner",
federalState: "BY",
metroCity: "Augsburg",
}),
);
expect(result.summary.byScope).toEqual(
expect.arrayContaining([expect.objectContaining({ scope: "CITY" })]),
);
expect(result.holidays).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "Augsburger Friedensfest",
date: "2026-08-08",
scope: "CITY",
}),
]),
);
});
it("rejects duplicate calendar scopes on create", async () => {
const db = {
country: {
findUnique: vi
.fn()
.mockResolvedValueOnce({ id: "country_de", name: "Deutschland" })
.mockResolvedValueOnce({ id: "country_de", name: "Deutschland" }),
},
metroCity: {
findUnique: vi.fn(),
},
holidayCalendar: {
findFirst: vi.fn().mockResolvedValue({ id: "existing_scope" }),
create: vi.fn(),
},
auditLog: {
create: vi.fn().mockResolvedValue({}),
},
};
const caller = createAdminCaller(db);
await expect(caller.createCalendar({
name: "Deutschland Standard",
scopeType: "COUNTRY",
countryId: "country_de",
priority: 0,
isActive: true,
})).rejects.toThrow("A holiday calendar for this exact scope already exists");
expect(db.holidayCalendar.create).not.toHaveBeenCalled();
});
it("rejects duplicate entry dates within the same calendar", async () => {
const db = {
holidayCalendar: {
findUnique: vi.fn().mockResolvedValue({ id: "cal_1", name: "Deutschland Standard" }),
},
holidayCalendarEntry: {
findFirst: vi.fn().mockResolvedValue({ id: "entry_existing" }),
create: vi.fn(),
},
auditLog: {
create: vi.fn().mockResolvedValue({}),
},
};
const caller = createAdminCaller(db);
await expect(caller.createEntry({
holidayCalendarId: "cal_1",
date: new Date("2026-12-24T00:00:00.000Z"),
name: "Heiligabend lokal",
isRecurringAnnual: true,
source: "manual",
})).rejects.toThrow("A holiday entry for this calendar and date already exists");
expect(db.holidayCalendarEntry.create).not.toHaveBeenCalled();
});
});