Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/booking/services/AvailabilityService.server.test.ts
T
2026-07-08 16:15:37 +02:00

98 lines
4.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { AvailabilityValidationError, listProviderAvailability, replaceProviderAvailability } from "./AvailabilityService.server";
const dbMock = vi.hoisted(() => ({
availabilityRule: {
findMany: vi.fn(),
deleteMany: vi.fn(),
createMany: vi.fn(),
},
availabilityException: {
findMany: vi.fn(),
deleteMany: vi.fn(),
createMany: vi.fn(),
},
$transaction: vi.fn(),
}));
vi.mock("~/utils/db.server", () => ({ db: dbMock }));
describe("AvailabilityService.server", () => {
beforeEach(() => {
vi.clearAllMocks();
dbMock.$transaction.mockImplementation(async (fn: (tx: typeof dbMock) => Promise<unknown>) => fn(dbMock));
});
it("loads and sorts provider availability", async () => {
dbMock.availabilityRule.findMany.mockResolvedValue([
{ id: "rule_2", dayOfWeek: "wednesday", startTime: "14:00", endTime: "17:00", timezone: "Europe/Berlin" },
{ id: "rule_1", dayOfWeek: "monday", startTime: "09:00", endTime: "12:00", timezone: "Europe/Berlin" },
]);
dbMock.availabilityException.findMany.mockResolvedValue([
{ id: "ex_2", date: new Date("2026-07-11T00:00:00Z"), kind: "custom_window", startTime: "10:00", endTime: "13:00", reason: "Workshop", timezone: "Europe/Berlin" },
{ id: "ex_1", date: new Date("2026-07-10T00:00:00Z"), kind: "unavailable", startTime: null, endTime: null, reason: "Urlaub", timezone: "Europe/Berlin" },
]);
await expect(listProviderAvailability("tenant_1", "user_1")).resolves.toEqual({
providerUserId: "user_1",
rules: [
{ id: "rule_1", dayOfWeek: "monday", startTime: "09:00", endTime: "12:00", timezone: "Europe/Berlin" },
{ id: "rule_2", dayOfWeek: "wednesday", startTime: "14:00", endTime: "17:00", timezone: "Europe/Berlin" },
],
exceptions: [
{ id: "ex_1", date: "2026-07-10", kind: "unavailable", startTime: null, endTime: null, reason: "Urlaub", timezone: "Europe/Berlin" },
{ id: "ex_2", date: "2026-07-11", kind: "custom_window", startTime: "10:00", endTime: "13:00", reason: "Workshop", timezone: "Europe/Berlin" },
],
});
});
it("replaces rules and exceptions atomically with normalized values", async () => {
await replaceProviderAvailability({
tenantId: "tenant_1",
providerUserId: "user_1",
rules: [{ dayOfWeek: "monday", startTime: "09:00", endTime: "12:00", timezone: " Europe/Berlin " }],
exceptions: [
{ date: "2026-07-10", kind: "unavailable", startTime: "09:00", endTime: "10:00", reason: " Urlaub ", timezone: "Europe/Berlin" },
{ date: "2026-07-11", kind: "custom_window", startTime: "10:00", endTime: "13:00", reason: null, timezone: "Europe/Berlin" },
],
});
expect(dbMock.$transaction).toHaveBeenCalledOnce();
expect(dbMock.availabilityRule.deleteMany).toHaveBeenCalledWith({ where: { tenantId: "tenant_1", providerUserId: "user_1" } });
expect(dbMock.availabilityException.deleteMany).toHaveBeenCalledWith({ where: { tenantId: "tenant_1", providerUserId: "user_1" } });
expect(dbMock.availabilityRule.createMany).toHaveBeenCalledWith({
data: [{ tenantId: "tenant_1", providerUserId: "user_1", dayOfWeek: "monday", startTime: "09:00", endTime: "12:00", timezone: "Europe/Berlin" }],
});
expect(dbMock.availabilityException.createMany).toHaveBeenCalledWith({
data: [
{ tenantId: "tenant_1", providerUserId: "user_1", date: new Date("2026-07-10T00:00:00Z"), kind: "unavailable", startTime: null, endTime: null, reason: "Urlaub", timezone: "Europe/Berlin" },
{ tenantId: "tenant_1", providerUserId: "user_1", date: new Date("2026-07-11T00:00:00Z"), kind: "custom_window", startTime: "10:00", endTime: "13:00", reason: null, timezone: "Europe/Berlin" },
],
});
});
it("rejects invalid time windows before writing", async () => {
await expect(
replaceProviderAvailability({
tenantId: "tenant_1",
providerUserId: "user_1",
rules: [{ dayOfWeek: "monday", startTime: "12:00", endTime: "09:00", timezone: "Europe/Berlin" }],
exceptions: [],
}),
).rejects.toBeInstanceOf(AvailabilityValidationError);
expect(dbMock.$transaction).not.toHaveBeenCalled();
});
it("requires custom-window exception times before writing", async () => {
await expect(
replaceProviderAvailability({
tenantId: "tenant_1",
providerUserId: "user_1",
rules: [],
exceptions: [{ date: "2026-07-10", kind: "custom_window", timezone: "Europe/Berlin" }],
}),
).rejects.toMatchObject({ code: "bookings.error.invalidTimeWindow" });
expect(dbMock.$transaction).not.toHaveBeenCalled();
});
});