refactor(api): extract timeline holiday procedures
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("../router/timeline-read-shared.js", () => ({
|
||||||
|
buildSelfServiceTimelineInput: vi.fn(),
|
||||||
|
buildTimelineEntriesDetailInput: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../router/timeline-holiday-load-support.js", () => ({
|
||||||
|
loadTimelineHolidayOverlays: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../router/timeline-holiday-support.js", () => ({
|
||||||
|
buildTimelineHolidayOverlayDetailResponse: vi.fn(),
|
||||||
|
formatTimelineHolidayOverlays: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildSelfServiceTimelineInput,
|
||||||
|
buildTimelineEntriesDetailInput,
|
||||||
|
} from "../router/timeline-read-shared.js";
|
||||||
|
import { loadTimelineHolidayOverlays } from "../router/timeline-holiday-load-support.js";
|
||||||
|
import {
|
||||||
|
buildTimelineHolidayOverlayDetailResponse,
|
||||||
|
formatTimelineHolidayOverlays,
|
||||||
|
} from "../router/timeline-holiday-support.js";
|
||||||
|
import {
|
||||||
|
readMyTimelineHolidayOverlays,
|
||||||
|
readTimelineHolidayOverlayDetail,
|
||||||
|
} from "../router/timeline-holiday-procedure-support.js";
|
||||||
|
|
||||||
|
const buildSelfServiceTimelineInputMock = vi.mocked(buildSelfServiceTimelineInput);
|
||||||
|
const buildTimelineEntriesDetailInputMock = vi.mocked(buildTimelineEntriesDetailInput);
|
||||||
|
const loadTimelineHolidayOverlaysMock = vi.mocked(loadTimelineHolidayOverlays);
|
||||||
|
const buildTimelineHolidayOverlayDetailResponseMock = vi.mocked(buildTimelineHolidayOverlayDetailResponse);
|
||||||
|
const formatTimelineHolidayOverlaysMock = vi.mocked(formatTimelineHolidayOverlays);
|
||||||
|
|
||||||
|
describe("timeline holiday procedure support", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an empty list when self-service scope cannot be resolved", async () => {
|
||||||
|
buildSelfServiceTimelineInputMock.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readMyTimelineHolidayOverlays(
|
||||||
|
{ db: {}, dbUser: { id: "user_1" } } as never,
|
||||||
|
{
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).resolves.toEqual([]);
|
||||||
|
|
||||||
|
expect(loadTimelineHolidayOverlaysMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads self-service holiday overlays when a scope is available", async () => {
|
||||||
|
const ctx = { db: {}, dbUser: { id: "user_1" } } as never;
|
||||||
|
const input = {
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
};
|
||||||
|
const selfServiceInput = {
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
resourceIds: ["resource_1"],
|
||||||
|
};
|
||||||
|
|
||||||
|
buildSelfServiceTimelineInputMock.mockResolvedValueOnce(selfServiceInput as never);
|
||||||
|
loadTimelineHolidayOverlaysMock.mockResolvedValueOnce([{ id: "overlay_1" }] as never);
|
||||||
|
|
||||||
|
await expect(readMyTimelineHolidayOverlays(ctx, input)).resolves.toEqual([{ id: "overlay_1" }]);
|
||||||
|
|
||||||
|
expect(buildSelfServiceTimelineInputMock).toHaveBeenCalledWith(ctx, input);
|
||||||
|
expect(loadTimelineHolidayOverlaysMock).toHaveBeenCalledWith(ctx.db, selfServiceInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds holiday overlay detail responses from resolved detail inputs", async () => {
|
||||||
|
buildTimelineEntriesDetailInputMock.mockReturnValueOnce({
|
||||||
|
period: {
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
},
|
||||||
|
filters: { countryCodes: ["DE"] },
|
||||||
|
timelineInput: {
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
countryCodes: ["DE"],
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
loadTimelineHolidayOverlaysMock.mockResolvedValueOnce([{ id: "overlay_1" }] as never);
|
||||||
|
formatTimelineHolidayOverlaysMock.mockReturnValueOnce([{ id: "formatted_1" }] as never);
|
||||||
|
buildTimelineHolidayOverlayDetailResponseMock.mockReturnValueOnce({ detail: true } as never);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readTimelineHolidayOverlayDetail({} as never, {
|
||||||
|
startDate: "2026-04-01",
|
||||||
|
endDate: "2026-04-07",
|
||||||
|
countryCodes: ["DE"],
|
||||||
|
}),
|
||||||
|
).resolves.toEqual({ detail: true });
|
||||||
|
|
||||||
|
expect(loadTimelineHolidayOverlaysMock).toHaveBeenCalledWith({}, {
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
countryCodes: ["DE"],
|
||||||
|
});
|
||||||
|
expect(formatTimelineHolidayOverlaysMock).toHaveBeenCalledWith([{ id: "overlay_1" }]);
|
||||||
|
expect(buildTimelineHolidayOverlayDetailResponseMock).toHaveBeenCalledWith({
|
||||||
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-07T00:00:00.000Z"),
|
||||||
|
filters: { countryCodes: ["DE"] },
|
||||||
|
overlays: [{ id: "formatted_1" }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
buildSelfServiceTimelineInput,
|
||||||
|
buildTimelineEntriesDetailInput,
|
||||||
|
} from "./timeline-read-shared.js";
|
||||||
|
import { loadTimelineHolidayOverlays } from "./timeline-holiday-load-support.js";
|
||||||
|
import {
|
||||||
|
buildTimelineHolidayOverlayDetailResponse,
|
||||||
|
formatTimelineHolidayOverlays,
|
||||||
|
} from "./timeline-holiday-support.js";
|
||||||
|
|
||||||
|
type TimelineHolidayProcedureDb = Parameters<typeof loadTimelineHolidayOverlays>[0];
|
||||||
|
type TimelineHolidaySelfServiceContext = Parameters<typeof buildSelfServiceTimelineInput>[0];
|
||||||
|
type TimelineHolidaySelfServiceInput = Parameters<typeof buildSelfServiceTimelineInput>[1];
|
||||||
|
type TimelineHolidayDetailInput = Parameters<typeof buildTimelineEntriesDetailInput>[0];
|
||||||
|
|
||||||
|
export async function readMyTimelineHolidayOverlays(
|
||||||
|
ctx: TimelineHolidaySelfServiceContext,
|
||||||
|
input: TimelineHolidaySelfServiceInput,
|
||||||
|
) {
|
||||||
|
const selfServiceInput = await buildSelfServiceTimelineInput(ctx, input);
|
||||||
|
if (!selfServiceInput) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadTimelineHolidayOverlays(ctx.db, selfServiceInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readTimelineHolidayOverlayDetail(
|
||||||
|
db: TimelineHolidayProcedureDb,
|
||||||
|
input: TimelineHolidayDetailInput,
|
||||||
|
) {
|
||||||
|
const { period, filters, timelineInput } = buildTimelineEntriesDetailInput(input);
|
||||||
|
const holidayOverlays = await loadTimelineHolidayOverlays(db, timelineInput);
|
||||||
|
|
||||||
|
return buildTimelineHolidayOverlayDetailResponse({
|
||||||
|
startDate: period.startDate,
|
||||||
|
endDate: period.endDate,
|
||||||
|
filters,
|
||||||
|
overlays: formatTimelineHolidayOverlays(holidayOverlays),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { controllerProcedure, protectedProcedure } from "../trpc.js";
|
import { controllerProcedure, protectedProcedure } from "../trpc.js";
|
||||||
import {
|
import {
|
||||||
buildSelfServiceTimelineInput,
|
TimelineDetailFiltersSchema,
|
||||||
createTimelineDateRange,
|
|
||||||
createTimelineFilters,
|
|
||||||
TimelineWindowFiltersSchema,
|
TimelineWindowFiltersSchema,
|
||||||
} from "./timeline-read-shared.js";
|
} from "./timeline-read-shared.js";
|
||||||
import {
|
import {
|
||||||
loadTimelineHolidayOverlays,
|
loadTimelineHolidayOverlays,
|
||||||
} from "./timeline-holiday-load-support.js";
|
} from "./timeline-holiday-load-support.js";
|
||||||
import {
|
import {
|
||||||
buildTimelineHolidayOverlayDetailResponse,
|
readMyTimelineHolidayOverlays,
|
||||||
|
readTimelineHolidayOverlayDetail,
|
||||||
|
} from "./timeline-holiday-procedure-support.js";
|
||||||
|
import {
|
||||||
formatTimelineHolidayOverlays,
|
formatTimelineHolidayOverlays,
|
||||||
summarizeTimelineHolidayOverlays,
|
summarizeTimelineHolidayOverlays,
|
||||||
type TimelineHolidayOverlayRecord,
|
type TimelineHolidayOverlayRecord,
|
||||||
@@ -40,44 +40,9 @@ export const timelineHolidayReadProcedures = {
|
|||||||
|
|
||||||
getMyHolidayOverlays: protectedProcedure
|
getMyHolidayOverlays: protectedProcedure
|
||||||
.input(TimelineWindowFiltersSchema)
|
.input(TimelineWindowFiltersSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => readMyTimelineHolidayOverlays(ctx, input)),
|
||||||
const selfServiceInput = await buildSelfServiceTimelineInput(ctx, input);
|
|
||||||
if (!selfServiceInput) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadTimelineHolidayOverlays(ctx.db, selfServiceInput);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getHolidayOverlayDetail: controllerProcedure
|
getHolidayOverlayDetail: controllerProcedure
|
||||||
.input(
|
.input(TimelineDetailFiltersSchema)
|
||||||
z.object({
|
.query(async ({ ctx, input }) => readTimelineHolidayOverlayDetail(ctx.db, input)),
|
||||||
startDate: z.string().optional(),
|
|
||||||
endDate: z.string().optional(),
|
|
||||||
durationDays: z.number().int().min(1).max(366).optional(),
|
|
||||||
resourceIds: z.array(z.string()).optional(),
|
|
||||||
projectIds: z.array(z.string()).optional(),
|
|
||||||
clientIds: z.array(z.string()).optional(),
|
|
||||||
chapters: z.array(z.string()).optional(),
|
|
||||||
eids: z.array(z.string()).optional(),
|
|
||||||
countryCodes: z.array(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const { startDate, endDate } = createTimelineDateRange(input);
|
|
||||||
const filters = createTimelineFilters(input);
|
|
||||||
const holidayOverlays = await loadTimelineHolidayOverlays(ctx.db, {
|
|
||||||
...filters,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
});
|
|
||||||
const formattedOverlays = formatHolidayOverlays(holidayOverlays);
|
|
||||||
|
|
||||||
return buildTimelineHolidayOverlayDetailResponse({
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
filters,
|
|
||||||
overlays: formattedOverlays,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user