Files
CapaKraken/packages/api/src/__tests__/holiday-calendar-catalog-read.test.ts
T
Hartmut a0de69a520 test(api): add 68 router tests for comment, project-lifecycle, dispo, holiday-calendar
Covers comment CRUD/resolve/delete, project status transitions and cascade
deletes, dispo import batch read/cancel/commit/resolve, and holiday calendar
catalog read with identifier fallback lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 16:37:02 +02:00

312 lines
12 KiB
TypeScript

import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { createCallerFactory, createTRPCRouter } from "../trpc.js";
import { holidayCalendarCatalogReadProcedures } from "../router/holiday-calendar-catalog-read.js";
// Pass-through so the db mock reaches the procedures unchanged
vi.mock("../router/holiday-calendar-shared.js", () => ({
asHolidayCalendarDb: (db: unknown) => db,
}));
// Provide the same shape the real support module exports
vi.mock("../router/holiday-calendar-support.js", () => ({
holidayCalendarListInclude: {
country: { select: { id: true, code: true, name: true } },
metroCity: { select: { id: true, name: true } },
entries: { orderBy: [{ date: "asc" }, { name: "asc" }] },
_count: { select: { entries: true } },
},
holidayCalendarDetailInclude: {
country: { select: { id: true, code: true, name: true } },
metroCity: { select: { id: true, name: true } },
entries: { orderBy: [{ date: "asc" }, { name: "asc" }] },
},
}));
const router = createTRPCRouter(holidayCalendarCatalogReadProcedures);
const createCaller = createCallerFactory(router);
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 },
});
}
function createUserCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "user@example.com", name: "User", image: null },
expires: "2099-01-01T00:00:00.000Z",
},
db: db as never,
dbUser: { id: "user_1", systemRole: SystemRole.USER, permissionOverrides: null },
});
}
const mockCalendar = {
id: "cal_1",
name: "German Federal Holidays",
scopeType: "COUNTRY",
stateCode: null,
isActive: true,
priority: 0,
country: { id: "country_de", code: "DE", name: "Germany" },
metroCity: null,
_count: { entries: 12 },
entries: [
{
id: "entry_1",
date: new Date("2026-01-01"),
name: "New Year",
isRecurringAnnual: true,
source: null,
},
],
};
// ─── listCalendars ────────────────────────────────────────────────────────────
describe("listCalendars", () => {
it("returns all active calendars when called with no input", async () => {
const findMany = vi.fn().mockResolvedValue([mockCalendar]);
const caller = createAdminCaller({ holidayCalendar: { findMany } });
const result = await caller.listCalendars();
expect(result).toEqual([mockCalendar]);
expect(findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ isActive: true }),
}),
);
});
it("applies countryCode filter uppercased with case-insensitive mode", async () => {
const findMany = vi.fn().mockResolvedValue([mockCalendar]);
const caller = createAdminCaller({ holidayCalendar: { findMany } });
await caller.listCalendars({ countryCode: "de" });
expect(findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
country: { code: { equals: "DE", mode: "insensitive" } },
}),
}),
);
});
it("includes inactive calendars when includeInactive is true", async () => {
const inactiveCalendar = { ...mockCalendar, id: "cal_inactive", isActive: false };
const findMany = vi.fn().mockResolvedValue([mockCalendar, inactiveCalendar]);
const caller = createAdminCaller({ holidayCalendar: { findMany } });
const result = await caller.listCalendars({ includeInactive: true });
expect(result).toHaveLength(2);
// isActive filter must NOT be present in the where clause
const whereArg = findMany.mock.calls[0][0].where;
expect(whereArg).not.toHaveProperty("isActive");
});
});
// ─── listCalendarsDetail ──────────────────────────────────────────────────────
describe("listCalendarsDetail", () => {
it("returns count and formatted calendar array", async () => {
const findMany = vi.fn().mockResolvedValue([mockCalendar]);
const caller = createAdminCaller({ holidayCalendar: { findMany } });
const result = await caller.listCalendarsDetail();
expect(result.count).toBe(1);
expect(result.calendars).toHaveLength(1);
expect(result.calendars[0]).toMatchObject({
id: "cal_1",
name: "German Federal Holidays",
scopeType: "COUNTRY",
stateCode: null,
isActive: true,
priority: 0,
country: { id: "country_de", code: "DE", name: "Germany" },
metroCity: null,
entryCount: 12,
});
});
it("formats entry dates as ISO date strings (YYYY-MM-DD)", async () => {
const findMany = vi.fn().mockResolvedValue([mockCalendar]);
const caller = createAdminCaller({ holidayCalendar: { findMany } });
const result = await caller.listCalendarsDetail();
expect(result.calendars[0].entries[0]).toMatchObject({
id: "entry_1",
date: "2026-01-01",
name: "New Year",
isRecurringAnnual: true,
source: null,
});
});
});
// ─── getCalendarByIdentifier ──────────────────────────────────────────────────
describe("getCalendarByIdentifier", () => {
it("finds a calendar by exact id on the first lookup", async () => {
const findUnique = vi.fn().mockResolvedValue(mockCalendar);
const findFirst = vi.fn();
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });
const result = await caller.getCalendarByIdentifier({ identifier: "cal_1" });
expect(result).toEqual(mockCalendar);
expect(findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "cal_1" } }));
expect(findFirst).not.toHaveBeenCalled();
});
it("falls back to exact name match when id lookup returns null", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const findFirst = vi.fn().mockResolvedValueOnce(mockCalendar);
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });
const result = await caller.getCalendarByIdentifier({ identifier: "German Federal Holidays" });
expect(result).toEqual(mockCalendar);
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: { name: { equals: "German Federal Holidays", mode: "insensitive" } },
}),
);
});
it("falls back to contains name match when exact name lookup returns null", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const findFirst = vi
.fn()
.mockResolvedValueOnce(null) // exact name miss
.mockResolvedValueOnce(mockCalendar); // contains name hit
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });
const result = await caller.getCalendarByIdentifier({ identifier: "Federal" });
expect(result).toEqual(mockCalendar);
expect(findFirst).toHaveBeenCalledTimes(2);
expect(findFirst).toHaveBeenLastCalledWith(
expect.objectContaining({
where: { name: { contains: "Federal", mode: "insensitive" } },
}),
);
});
it("throws NOT_FOUND when no match is found at all", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const findFirst = vi.fn().mockResolvedValue(null);
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });
await expect(
caller.getCalendarByIdentifier({ identifier: "does-not-exist" }),
).rejects.toMatchObject({
code: "NOT_FOUND",
message: "Holiday calendar not found: does-not-exist",
});
});
});
// ─── getCalendarByIdentifierDetail ────────────────────────────────────────────
describe("getCalendarByIdentifierDetail", () => {
it("returns formatted detail for a matching calendar", async () => {
const findUnique = vi.fn().mockResolvedValue(mockCalendar);
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst: vi.fn() } });
const result = await caller.getCalendarByIdentifierDetail({ identifier: "cal_1" });
expect(result).toMatchObject({
id: "cal_1",
name: "German Federal Holidays",
scopeType: "COUNTRY",
isActive: true,
entryCount: 12,
entries: [expect.objectContaining({ date: "2026-01-01" })],
});
});
it("throws NOT_FOUND when identifier does not match any calendar", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const findFirst = vi.fn().mockResolvedValue(null);
const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });
await expect(
caller.getCalendarByIdentifierDetail({ identifier: "no-such-calendar" }),
).rejects.toMatchObject({ code: "NOT_FOUND" });
});
});
// ─── getCalendarById ──────────────────────────────────────────────────────────
describe("getCalendarById", () => {
it("returns the calendar when found by id", async () => {
const findUnique = vi.fn().mockResolvedValue(mockCalendar);
const caller = createAdminCaller({ holidayCalendar: { findUnique } });
const result = await caller.getCalendarById({ id: "cal_1" });
expect(result).toEqual(mockCalendar);
expect(findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "cal_1" } }));
});
it("throws NOT_FOUND when no calendar exists with the given id", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const caller = createAdminCaller({ holidayCalendar: { findUnique } });
await expect(caller.getCalendarById({ id: "cal_missing" })).rejects.toMatchObject({
code: "NOT_FOUND",
message: "Holiday calendar not found",
});
});
});
// ─── Authorization ────────────────────────────────────────────────────────────
describe("Authorization", () => {
it("rejects a non-admin user (SystemRole.USER) with FORBIDDEN on all procedures", async () => {
const findMany = vi.fn();
const findUnique = vi.fn();
const findFirst = vi.fn();
const caller = createUserCaller({ holidayCalendar: { findMany, findUnique, findFirst } });
await expect(caller.listCalendars()).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.listCalendarsDetail()).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.getCalendarByIdentifier({ identifier: "cal_1" })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(
caller.getCalendarByIdentifierDetail({ identifier: "cal_1" }),
).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
await expect(caller.getCalendarById({ id: "cal_1" })).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
expect(findMany).not.toHaveBeenCalled();
expect(findUnique).not.toHaveBeenCalled();
expect(findFirst).not.toHaveBeenCalled();
});
});