@htlkg/data 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/client/index.d.ts +257 -1
  2. package/dist/client/index.js +59 -1
  3. package/dist/client/index.js.map +1 -1
  4. package/dist/common-DSxswsZ3.d.ts +40 -0
  5. package/dist/hooks/index.d.ts +162 -10
  6. package/dist/hooks/index.js +191 -33
  7. package/dist/hooks/index.js.map +1 -1
  8. package/dist/index.d.ts +9 -5
  9. package/dist/index.js +789 -13
  10. package/dist/index.js.map +1 -1
  11. package/dist/mutations/index.d.ts +342 -4
  12. package/dist/mutations/index.js +486 -0
  13. package/dist/mutations/index.js.map +1 -1
  14. package/dist/{productInstances-BA3cNsYc.d.ts → productInstances-BpQv1oLS.d.ts} +2 -40
  15. package/dist/queries/index.d.ts +113 -2
  16. package/dist/queries/index.js +192 -1
  17. package/dist/queries/index.js.map +1 -1
  18. package/dist/reservations-C0FNm__0.d.ts +154 -0
  19. package/dist/reservations-CdDfkcZ_.d.ts +172 -0
  20. package/package.json +14 -13
  21. package/src/client/index.ts +18 -0
  22. package/src/client/reservations.ts +336 -0
  23. package/src/hooks/createDataHook.test.ts +534 -0
  24. package/src/hooks/createDataHook.ts +20 -13
  25. package/src/hooks/index.ts +2 -0
  26. package/src/hooks/useContacts.test.ts +159 -0
  27. package/src/hooks/useContacts.ts +176 -0
  28. package/src/hooks/useReservations.ts +145 -0
  29. package/src/mutations/contacts.test.ts +604 -0
  30. package/src/mutations/contacts.ts +554 -0
  31. package/src/mutations/index.ts +32 -0
  32. package/src/mutations/productInstances/productInstances.test.ts +3 -3
  33. package/src/mutations/reservations.test.ts +459 -0
  34. package/src/mutations/reservations.ts +452 -0
  35. package/src/queries/contacts.test.ts +505 -0
  36. package/src/queries/contacts.ts +237 -0
  37. package/src/queries/index.ts +21 -0
  38. package/src/queries/reservations.test.ts +374 -0
  39. package/src/queries/reservations.ts +247 -0
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Reservation Mutation Tests
3
+ *
4
+ * Tests for reservation CRUD operations including date validation,
5
+ * status transitions, soft delete, and restoration.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach } from "vitest";
9
+ import {
10
+ createReservation,
11
+ updateReservation,
12
+ softDeleteReservation,
13
+ restoreReservation,
14
+ deleteReservation,
15
+ updateReservationStatus,
16
+ ReservationValidationError,
17
+ type ReservationStatus,
18
+ } from "./reservations";
19
+
20
+ // Mock the systemSettings query functions
21
+ vi.mock("../queries/systemSettings", () => ({
22
+ checkRestoreEligibility: vi.fn((deletedAt: string | null, retentionDays: number) => {
23
+ if (!deletedAt) {
24
+ return { canRestore: false, daysRemaining: 0, daysExpired: 0 };
25
+ }
26
+ const deletedDate = new Date(deletedAt);
27
+ const now = new Date();
28
+ const diffMs = now.getTime() - deletedDate.getTime();
29
+ const diffDays = diffMs / (1000 * 60 * 60 * 24);
30
+
31
+ if (diffDays > retentionDays) {
32
+ return {
33
+ canRestore: false,
34
+ daysRemaining: 0,
35
+ daysExpired: Math.floor(diffDays - retentionDays),
36
+ };
37
+ }
38
+
39
+ return {
40
+ canRestore: true,
41
+ daysRemaining: Math.ceil(retentionDays - diffDays),
42
+ daysExpired: 0,
43
+ };
44
+ }),
45
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS: 30,
46
+ }));
47
+
48
+ describe("Reservation Mutations", () => {
49
+ describe("createReservation", () => {
50
+ const validInput = {
51
+ brandId: "brand-123",
52
+ visitId: "visit-456",
53
+ confirmationCode: "ABC123",
54
+ checkIn: "2024-06-01",
55
+ checkOut: "2024-06-05",
56
+ status: "confirmed" as const,
57
+ source: "direct",
58
+ room: "101",
59
+ };
60
+
61
+ it("should create a reservation with valid dates", async () => {
62
+ const mockCreate = vi.fn().mockResolvedValue({
63
+ data: { id: "reservation-001", ...validInput },
64
+ errors: null,
65
+ });
66
+
67
+ const mockClient = {
68
+ models: {
69
+ Reservation: { create: mockCreate },
70
+ },
71
+ };
72
+
73
+ const result = await createReservation(mockClient, validInput);
74
+
75
+ expect(result).toBeTruthy();
76
+ expect(result?.id).toBe("reservation-001");
77
+ expect(mockCreate).toHaveBeenCalledTimes(1);
78
+ });
79
+
80
+ it("should calculate nights automatically when not provided", async () => {
81
+ const mockCreate = vi.fn().mockResolvedValue({
82
+ data: { id: "reservation-001" },
83
+ errors: null,
84
+ });
85
+
86
+ const mockClient = {
87
+ models: {
88
+ Reservation: { create: mockCreate },
89
+ },
90
+ };
91
+
92
+ await createReservation(mockClient, validInput);
93
+
94
+ const callArgs = mockCreate.mock.calls[0][0];
95
+ expect(callArgs.nights).toBe(4); // June 1 to June 5 = 4 nights
96
+ });
97
+
98
+ it("should throw ReservationValidationError for invalid check-in date", async () => {
99
+ const mockClient = {
100
+ models: {
101
+ Reservation: { create: vi.fn() },
102
+ },
103
+ };
104
+
105
+ const invalidInput = {
106
+ ...validInput,
107
+ checkIn: "invalid-date",
108
+ };
109
+
110
+ await expect(createReservation(mockClient, invalidInput)).rejects.toThrow(
111
+ ReservationValidationError
112
+ );
113
+ });
114
+
115
+ it("should throw ReservationValidationError when check-out is before check-in", async () => {
116
+ const mockClient = {
117
+ models: {
118
+ Reservation: { create: vi.fn() },
119
+ },
120
+ };
121
+
122
+ const invalidInput = {
123
+ ...validInput,
124
+ checkIn: "2024-06-05",
125
+ checkOut: "2024-06-01",
126
+ };
127
+
128
+ await expect(createReservation(mockClient, invalidInput)).rejects.toThrow(
129
+ "Check-out date must be after check-in date"
130
+ );
131
+ });
132
+
133
+ it("should throw ReservationValidationError for minimum stay violation", async () => {
134
+ const mockClient = {
135
+ models: {
136
+ Reservation: { create: vi.fn() },
137
+ },
138
+ };
139
+
140
+ const invalidInput = {
141
+ ...validInput,
142
+ checkIn: "2024-06-01T10:00:00",
143
+ checkOut: "2024-06-01T11:00:00", // Only 1 hour
144
+ };
145
+
146
+ await expect(createReservation(mockClient, invalidInput)).rejects.toThrow(
147
+ "Minimum stay is 4 hours"
148
+ );
149
+ });
150
+
151
+ it("should return null when GraphQL returns errors", async () => {
152
+ const mockCreate = vi.fn().mockResolvedValue({
153
+ data: null,
154
+ errors: [{ message: "Database error" }],
155
+ });
156
+
157
+ const mockClient = {
158
+ models: {
159
+ Reservation: { create: mockCreate },
160
+ },
161
+ };
162
+
163
+ const result = await createReservation(mockClient, validInput);
164
+
165
+ expect(result).toBeNull();
166
+ });
167
+ });
168
+
169
+ describe("updateReservation", () => {
170
+ it("should update a reservation without status change", async () => {
171
+ const mockUpdate = vi.fn().mockResolvedValue({
172
+ data: { id: "reservation-001", room: "202" },
173
+ errors: null,
174
+ });
175
+
176
+ const mockClient = {
177
+ models: {
178
+ Reservation: { update: mockUpdate },
179
+ },
180
+ };
181
+
182
+ const result = await updateReservation(mockClient, {
183
+ id: "reservation-001",
184
+ room: "202",
185
+ });
186
+
187
+ expect(result).toBeTruthy();
188
+ expect(mockUpdate).toHaveBeenCalledWith({ id: "reservation-001", room: "202" });
189
+ });
190
+
191
+ it("should validate status transition when status is being changed", async () => {
192
+ const mockGet = vi.fn().mockResolvedValue({
193
+ data: { id: "reservation-001", status: "checked_out" },
194
+ errors: null,
195
+ });
196
+
197
+ const mockClient = {
198
+ models: {
199
+ Reservation: {
200
+ get: mockGet,
201
+ update: vi.fn(),
202
+ },
203
+ },
204
+ };
205
+
206
+ // Try to change from checked_out to confirmed (not allowed)
207
+ await expect(
208
+ updateReservation(mockClient, {
209
+ id: "reservation-001",
210
+ status: "confirmed",
211
+ })
212
+ ).rejects.toThrow(ReservationValidationError);
213
+ });
214
+
215
+ it("should recalculate nights when dates are updated", async () => {
216
+ const mockUpdate = vi.fn().mockResolvedValue({
217
+ data: { id: "reservation-001" },
218
+ errors: null,
219
+ });
220
+
221
+ const mockClient = {
222
+ models: {
223
+ Reservation: { update: mockUpdate },
224
+ },
225
+ };
226
+
227
+ await updateReservation(mockClient, {
228
+ id: "reservation-001",
229
+ checkIn: "2024-06-01",
230
+ checkOut: "2024-06-10",
231
+ });
232
+
233
+ const callArgs = mockUpdate.mock.calls[0][0];
234
+ expect(callArgs.nights).toBe(9); // 9 nights
235
+ });
236
+ });
237
+
238
+ describe("updateReservationStatus", () => {
239
+ const statusTransitionTests: Array<{
240
+ from: ReservationStatus;
241
+ to: ReservationStatus;
242
+ allowed: boolean;
243
+ }> = [
244
+ { from: "confirmed", to: "checked_in", allowed: true },
245
+ { from: "confirmed", to: "cancelled", allowed: true },
246
+ { from: "confirmed", to: "no_show", allowed: true },
247
+ { from: "confirmed", to: "checked_out", allowed: false },
248
+ { from: "checked_in", to: "checked_out", allowed: true },
249
+ { from: "checked_in", to: "cancelled", allowed: true },
250
+ { from: "checked_in", to: "confirmed", allowed: false },
251
+ { from: "checked_out", to: "confirmed", allowed: false },
252
+ { from: "checked_out", to: "checked_in", allowed: false },
253
+ { from: "cancelled", to: "confirmed", allowed: false },
254
+ { from: "no_show", to: "confirmed", allowed: false },
255
+ ];
256
+
257
+ statusTransitionTests.forEach(({ from, to, allowed }) => {
258
+ it(`should ${allowed ? "allow" : "reject"} transition from '${from}' to '${to}'`, async () => {
259
+ const mockGet = vi.fn().mockResolvedValue({
260
+ data: { id: "reservation-001", status: from },
261
+ errors: null,
262
+ });
263
+ const mockUpdate = vi.fn().mockResolvedValue({
264
+ data: { id: "reservation-001", status: to },
265
+ errors: null,
266
+ });
267
+
268
+ const mockClient = {
269
+ models: {
270
+ Reservation: {
271
+ get: mockGet,
272
+ update: mockUpdate,
273
+ },
274
+ },
275
+ };
276
+
277
+ if (allowed) {
278
+ const result = await updateReservationStatus(mockClient, "reservation-001", to);
279
+ expect(result).toBeTruthy();
280
+ expect(mockUpdate).toHaveBeenCalledWith({ id: "reservation-001", status: to });
281
+ } else {
282
+ await expect(
283
+ updateReservationStatus(mockClient, "reservation-001", to)
284
+ ).rejects.toThrow(ReservationValidationError);
285
+ }
286
+ });
287
+ });
288
+
289
+ it("should return null when reservation not found", async () => {
290
+ const mockGet = vi.fn().mockResolvedValue({
291
+ data: null,
292
+ errors: [{ message: "Not found" }],
293
+ });
294
+
295
+ const mockClient = {
296
+ models: {
297
+ Reservation: { get: mockGet, update: vi.fn() },
298
+ },
299
+ };
300
+
301
+ const result = await updateReservationStatus(mockClient, "nonexistent", "checked_in");
302
+
303
+ expect(result).toBeNull();
304
+ });
305
+ });
306
+
307
+ describe("softDeleteReservation", () => {
308
+ it("should set deletedAt and deletedBy", async () => {
309
+ const mockUpdate = vi.fn().mockResolvedValue({
310
+ data: { id: "reservation-001" },
311
+ errors: null,
312
+ });
313
+
314
+ const mockClient = {
315
+ models: {
316
+ Reservation: { update: mockUpdate },
317
+ },
318
+ };
319
+
320
+ const result = await softDeleteReservation(
321
+ mockClient,
322
+ "reservation-001",
323
+ "admin@example.com"
324
+ );
325
+
326
+ expect(result).toBe(true);
327
+ const callArgs = mockUpdate.mock.calls[0][0];
328
+ expect(callArgs.id).toBe("reservation-001");
329
+ expect(callArgs.deletedAt).toBeDefined();
330
+ expect(callArgs.deletedBy).toBe("admin@example.com");
331
+ });
332
+
333
+ it("should return false on GraphQL error", async () => {
334
+ const mockUpdate = vi.fn().mockResolvedValue({
335
+ data: null,
336
+ errors: [{ message: "Error" }],
337
+ });
338
+
339
+ const mockClient = {
340
+ models: {
341
+ Reservation: { update: mockUpdate },
342
+ },
343
+ };
344
+
345
+ const result = await softDeleteReservation(
346
+ mockClient,
347
+ "reservation-001",
348
+ "admin@example.com"
349
+ );
350
+
351
+ expect(result).toBe(false);
352
+ });
353
+ });
354
+
355
+ describe("restoreReservation", () => {
356
+ it("should restore a recently deleted reservation", async () => {
357
+ const recentDeletedAt = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(); // 5 days ago
358
+
359
+ const mockGet = vi.fn().mockResolvedValue({
360
+ data: { id: "reservation-001", deletedAt: recentDeletedAt },
361
+ errors: null,
362
+ });
363
+ const mockUpdate = vi.fn().mockResolvedValue({
364
+ data: { id: "reservation-001" },
365
+ errors: null,
366
+ });
367
+
368
+ const mockClient = {
369
+ models: {
370
+ Reservation: { get: mockGet, update: mockUpdate },
371
+ },
372
+ };
373
+
374
+ const result = await restoreReservation(mockClient, "reservation-001");
375
+
376
+ expect(result.success).toBe(true);
377
+ expect(mockUpdate).toHaveBeenCalledWith({
378
+ id: "reservation-001",
379
+ deletedAt: null,
380
+ deletedBy: null,
381
+ });
382
+ });
383
+
384
+ it("should reject restoration after retention period expires", async () => {
385
+ const oldDeletedAt = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(); // 60 days ago
386
+
387
+ const mockGet = vi.fn().mockResolvedValue({
388
+ data: { id: "reservation-001", deletedAt: oldDeletedAt },
389
+ errors: null,
390
+ });
391
+
392
+ const mockClient = {
393
+ models: {
394
+ Reservation: { get: mockGet, update: vi.fn() },
395
+ },
396
+ };
397
+
398
+ const result = await restoreReservation(mockClient, "reservation-001", 30);
399
+
400
+ expect(result.success).toBe(false);
401
+ expect(result.error).toContain("Retention period");
402
+ });
403
+
404
+ it("should return error when reservation not found", async () => {
405
+ const mockGet = vi.fn().mockResolvedValue({
406
+ data: null,
407
+ errors: [{ message: "Not found" }],
408
+ });
409
+
410
+ const mockClient = {
411
+ models: {
412
+ Reservation: { get: mockGet, update: vi.fn() },
413
+ },
414
+ };
415
+
416
+ const result = await restoreReservation(mockClient, "nonexistent");
417
+
418
+ expect(result.success).toBe(false);
419
+ expect(result.error).toBe("Reservation not found");
420
+ });
421
+ });
422
+
423
+ describe("deleteReservation", () => {
424
+ it("should permanently delete a reservation", async () => {
425
+ const mockDelete = vi.fn().mockResolvedValue({
426
+ data: { id: "reservation-001" },
427
+ errors: null,
428
+ });
429
+
430
+ const mockClient = {
431
+ models: {
432
+ Reservation: { delete: mockDelete },
433
+ },
434
+ };
435
+
436
+ const result = await deleteReservation(mockClient, "reservation-001");
437
+
438
+ expect(result).toBe(true);
439
+ expect(mockDelete).toHaveBeenCalledWith({ id: "reservation-001" });
440
+ });
441
+
442
+ it("should return false on GraphQL error", async () => {
443
+ const mockDelete = vi.fn().mockResolvedValue({
444
+ data: null,
445
+ errors: [{ message: "Cannot delete" }],
446
+ });
447
+
448
+ const mockClient = {
449
+ models: {
450
+ Reservation: { delete: mockDelete },
451
+ },
452
+ };
453
+
454
+ const result = await deleteReservation(mockClient, "reservation-001");
455
+
456
+ expect(result).toBe(false);
457
+ });
458
+ });
459
+ });