98 lines
4.7 KiB
TypeScript
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();
|
|
});
|
|
});
|