@stacksjs/commerce 0.70.53 → 0.70.55

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 (165) hide show
  1. package/package.json +3 -2
  2. package/src/carts/destroy.ts +54 -0
  3. package/src/carts/fetch.ts +20 -0
  4. package/src/carts/index.ts +13 -0
  5. package/src/carts/store.ts +75 -0
  6. package/src/carts/update.ts +78 -0
  7. package/src/coupons/destroy.ts +49 -0
  8. package/src/coupons/fetch.ts +510 -0
  9. package/src/coupons/index.ts +30 -0
  10. package/src/coupons/store.ts +46 -0
  11. package/src/coupons/update.ts +147 -0
  12. package/src/customers/destroy.ts +62 -0
  13. package/src/customers/fetch.ts +20 -0
  14. package/src/customers/index.ts +17 -0
  15. package/src/customers/store.ts +45 -0
  16. package/src/customers/update.ts +44 -0
  17. package/src/devices/destroy.ts +54 -0
  18. package/src/devices/export.ts +98 -0
  19. package/src/devices/fetch.ts +125 -0
  20. package/src/devices/index.ts +31 -0
  21. package/src/devices/store.ts +82 -0
  22. package/src/devices/update.ts +125 -0
  23. package/src/errors/destroy.ts +54 -0
  24. package/src/errors/fetch.ts +197 -0
  25. package/src/errors/index.ts +28 -0
  26. package/src/errors/update.ts +116 -0
  27. package/src/gift-cards/destroy.ts +87 -0
  28. package/src/gift-cards/fetch.ts +431 -0
  29. package/src/gift-cards/index.ts +26 -0
  30. package/src/gift-cards/store.ts +56 -0
  31. package/src/gift-cards/update.ts +102 -0
  32. package/src/index.ts +78 -0
  33. package/src/orders/destroy.ts +111 -0
  34. package/src/orders/events.ts +121 -0
  35. package/src/orders/export.ts +158 -0
  36. package/src/orders/fetch.ts +379 -0
  37. package/src/orders/guards.ts +261 -0
  38. package/src/orders/index.ts +91 -0
  39. package/src/orders/place-order.ts +274 -0
  40. package/src/orders/store.ts +51 -0
  41. package/src/orders/totals.ts +190 -0
  42. package/src/orders/update.ts +164 -0
  43. package/src/orders/webhook.ts +312 -0
  44. package/src/payments/destroy.ts +56 -0
  45. package/src/payments/fetch.ts +288 -0
  46. package/src/payments/index.ts +16 -0
  47. package/src/payments/store.ts +63 -0
  48. package/src/payments/update.ts +52 -0
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +103 -0
  51. package/src/products/categories/fetch.ts +285 -0
  52. package/src/products/categories/index.ts +36 -0
  53. package/src/products/categories/store.ts +87 -0
  54. package/src/products/categories/update.ts +251 -0
  55. package/src/products/index.ts +35 -0
  56. package/src/products/items/destroy.ts +54 -0
  57. package/src/products/items/fetch.ts +282 -0
  58. package/src/products/items/index.ts +26 -0
  59. package/src/products/items/store.ts +84 -0
  60. package/src/products/items/update.ts +324 -0
  61. package/src/products/manufacturers/destroy.ts +56 -0
  62. package/src/products/manufacturers/fetch.ts +117 -0
  63. package/src/products/manufacturers/index.ts +24 -0
  64. package/src/products/manufacturers/store.ts +82 -0
  65. package/src/products/manufacturers/update.ts +113 -0
  66. package/src/products/reviews/destroy.ts +56 -0
  67. package/src/products/reviews/fetch.ts +63 -0
  68. package/src/products/reviews/index.ts +25 -0
  69. package/src/products/reviews/store.ts +46 -0
  70. package/src/products/reviews/update.ts +82 -0
  71. package/src/products/units/destroy.ts +56 -0
  72. package/src/products/units/fetch.ts +20 -0
  73. package/src/products/units/index.ts +13 -0
  74. package/src/products/units/store.ts +162 -0
  75. package/src/products/units/update.ts +159 -0
  76. package/src/products/variants/destroy.ts +56 -0
  77. package/src/products/variants/fetch.ts +20 -0
  78. package/src/products/variants/index.ts +21 -0
  79. package/src/products/variants/store.ts +136 -0
  80. package/src/products/variants/update.ts +104 -0
  81. package/src/receipts/destroy.ts +54 -0
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +222 -0
  84. package/src/receipts/index.ts +24 -0
  85. package/src/receipts/printer.ts +69 -0
  86. package/src/receipts/store.ts +69 -0
  87. package/src/receipts/update.ts +124 -0
  88. package/src/shippings/delivery-routes/destroy.ts +54 -0
  89. package/src/shippings/delivery-routes/fetch.ts +42 -0
  90. package/src/shippings/delivery-routes/index.ts +26 -0
  91. package/src/shippings/delivery-routes/store.ts +69 -0
  92. package/src/shippings/delivery-routes/update.ts +120 -0
  93. package/src/shippings/digital-deliveries/destroy.ts +111 -0
  94. package/src/shippings/digital-deliveries/fetch.ts +20 -0
  95. package/src/shippings/digital-deliveries/index.ts +26 -0
  96. package/src/shippings/digital-deliveries/store.ts +76 -0
  97. package/src/shippings/digital-deliveries/update.ts +129 -0
  98. package/src/shippings/drivers/destroy.ts +78 -0
  99. package/src/shippings/drivers/fetch.ts +20 -0
  100. package/src/shippings/drivers/index.ts +24 -0
  101. package/src/shippings/drivers/store.ts +69 -0
  102. package/src/shippings/drivers/update.ts +117 -0
  103. package/src/shippings/index.ts +39 -0
  104. package/src/shippings/license-keys/destroy.ts +111 -0
  105. package/src/shippings/license-keys/fetch.ts +20 -0
  106. package/src/shippings/license-keys/index.ts +26 -0
  107. package/src/shippings/license-keys/store.ts +77 -0
  108. package/src/shippings/license-keys/update.ts +117 -0
  109. package/src/shippings/shipping-methods/destroy.ts +111 -0
  110. package/src/shippings/shipping-methods/fetch.ts +62 -0
  111. package/src/shippings/shipping-methods/index.ts +28 -0
  112. package/src/shippings/shipping-methods/store.ts +123 -0
  113. package/src/shippings/shipping-methods/update.ts +120 -0
  114. package/src/shippings/shipping-rates/destroy.ts +102 -0
  115. package/src/shippings/shipping-rates/fetch.ts +320 -0
  116. package/src/shippings/shipping-rates/index.ts +36 -0
  117. package/src/shippings/shipping-rates/store.ts +102 -0
  118. package/src/shippings/shipping-rates/update.ts +139 -0
  119. package/src/shippings/shipping-zones/destroy.ts +111 -0
  120. package/src/shippings/shipping-zones/fetch.ts +96 -0
  121. package/src/shippings/shipping-zones/index.ts +26 -0
  122. package/src/shippings/shipping-zones/store.ts +74 -0
  123. package/src/shippings/shipping-zones/update.ts +156 -0
  124. package/src/tax/destroy.ts +54 -0
  125. package/src/tax/fetch.ts +20 -0
  126. package/src/tax/index.ts +24 -0
  127. package/src/tax/store.ts +69 -0
  128. package/src/tax/update.ts +119 -0
  129. package/src/tests/coupons.test.ts +90 -0
  130. package/src/tests/customers.test.ts +39 -0
  131. package/src/tests/device.test.ts +111 -0
  132. package/src/tests/digitals.test.ts +31 -0
  133. package/src/tests/drivers.test.ts +25 -0
  134. package/src/tests/gift-cards.test.ts +36 -0
  135. package/src/tests/license.test.ts +31 -0
  136. package/src/tests/manufacturers.test.ts +80 -0
  137. package/src/tests/orders.test.ts +25 -0
  138. package/src/tests/payments.test.ts +75 -0
  139. package/src/tests/rates.test.ts +367 -0
  140. package/src/tests/receipts.test.ts +287 -0
  141. package/src/tests/restaurant.test.ts +522 -0
  142. package/src/tests/reviews.test.ts +165 -0
  143. package/src/tests/routes.test.ts +51 -0
  144. package/src/tests/setup.ts +149 -0
  145. package/src/tests/shipping.test.ts +88 -0
  146. package/src/tests/tax.test.ts +25 -0
  147. package/src/tests/units.test.ts +110 -0
  148. package/src/tests/variants.test.ts +133 -0
  149. package/src/tests/waitlist.test.ts +303 -0
  150. package/src/tests/zones.test.ts +86 -0
  151. package/src/types.ts +312 -0
  152. package/src/utils/typed-stats.ts +67 -0
  153. package/src/waitlists/index.ts +19 -0
  154. package/src/waitlists/products/destroy.ts +65 -0
  155. package/src/waitlists/products/export.ts +113 -0
  156. package/src/waitlists/products/fetch.ts +362 -0
  157. package/src/waitlists/products/index.ts +33 -0
  158. package/src/waitlists/products/store.ts +112 -0
  159. package/src/waitlists/products/update.ts +125 -0
  160. package/src/waitlists/restaurant/destroy.ts +65 -0
  161. package/src/waitlists/restaurant/export.ts +107 -0
  162. package/src/waitlists/restaurant/fetch.ts +538 -0
  163. package/src/waitlists/restaurant/index.ts +43 -0
  164. package/src/waitlists/restaurant/store.ts +109 -0
  165. package/src/waitlists/restaurant/update.ts +198 -0
@@ -0,0 +1,522 @@
1
+ type WaitlistRestaurantJsonResponse = ModelRow<typeof WaitlistRestaurant>
2
+ import { beforeEach, describe, expect, it } from 'bun:test'
3
+ import { refreshDatabase } from './setup'
4
+ import { bulkDestroy } from '../waitlists/restaurant/destroy'
5
+ import {
6
+ fetchBetweenDates,
7
+ fetchSeatedBetweenDates,
8
+ fetchTablesTurnedToday,
9
+ fetchWaiting,
10
+ fetchWaitingWithPartySizes,
11
+ fetchWaitingWithQuotedTimes,
12
+ } from '../waitlists/restaurant/fetch'
13
+ import { bulkStore } from '../waitlists/restaurant/store'
14
+
15
+ beforeEach(async () => {
16
+ await refreshDatabase()
17
+ })
18
+
19
+ describe('Restaurant Waitlist Module', () => {
20
+ describe('store', () => {
21
+ it('should return 0 when trying to bulk store an empty array', async () => {
22
+ const count = await bulkStore([])
23
+ expect(count).toBe(0)
24
+ })
25
+ })
26
+
27
+ describe('destroy', () => {
28
+ it('should return 0 when trying to delete an empty array of waitlist entries', async () => {
29
+ // Try to delete with an empty array
30
+ const deletedCount = await bulkDestroy([])
31
+ expect(deletedCount).toBe(0)
32
+ })
33
+ })
34
+
35
+ describe('fetch', () => {
36
+ it('should fetch waitlist entries between two dates', async () => {
37
+ // Create test dates using today
38
+ const today = new Date()
39
+ const startDate = new Date(today.setHours(0, 0, 0, 0))
40
+ const endDate = new Date(today.setHours(23, 59, 59, 999))
41
+
42
+ // Create test waitlist entries
43
+ const requests = [
44
+ {
45
+ name: 'John Doe',
46
+ email: 'john@example.com',
47
+ party_size: 4,
48
+ check_in_time: Date.now(),
49
+ table_preference: 'indoor',
50
+ status: 'waiting',
51
+ quoted_wait_time: 30,
52
+ customer_id: 1,
53
+ },
54
+ {
55
+ name: 'Jane Smith',
56
+ email: 'jane@example.com',
57
+ party_size: 2,
58
+ check_in_time: Date.now(),
59
+ table_preference: 'bar',
60
+ status: 'waiting',
61
+ quoted_wait_time: 15,
62
+ customer_id: 2,
63
+ },
64
+ {
65
+ name: 'Bob Wilson',
66
+ email: 'bob@example.com',
67
+ party_size: 6,
68
+ check_in_time: Date.now(),
69
+ table_preference: 'booth',
70
+ status: 'waiting',
71
+ quoted_wait_time: 45,
72
+ customer_id: 3,
73
+ },
74
+ ]
75
+
76
+ // Create the waitlist entries
77
+ await bulkStore(requests)
78
+
79
+ // Fetch entries between dates
80
+ const entries = await fetchBetweenDates(startDate, endDate)
81
+ expect(entries).toBeDefined()
82
+ expect(entries.length).toBe(3) // All entries should be within the date range
83
+ expect(entries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('John Doe')
84
+ expect(entries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('Jane Smith')
85
+ expect(entries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('Bob Wilson')
86
+ })
87
+
88
+ it('should fetch seated waitlist entries between two dates', async () => {
89
+ // Create test dates using today
90
+ const today = new Date()
91
+ const startDate = new Date(today.setHours(0, 0, 0, 0))
92
+ const endDate = new Date(today.setHours(23, 59, 59, 999))
93
+
94
+ // Create test waitlist entries with different statuses
95
+ const requests = [
96
+ {
97
+ name: 'John Doe',
98
+ email: 'john@example.com',
99
+ party_size: 4,
100
+ check_in_time: Date.now(),
101
+ table_preference: 'indoor',
102
+ status: 'seated',
103
+ quoted_wait_time: 30,
104
+ customer_id: 1,
105
+ },
106
+ {
107
+ name: 'Jane Smith',
108
+ email: 'jane@example.com',
109
+ party_size: 2,
110
+ check_in_time: Date.now(),
111
+ table_preference: 'bar',
112
+ status: 'seated',
113
+ quoted_wait_time: 15,
114
+ customer_id: 2,
115
+ },
116
+ {
117
+ name: 'Bob Wilson',
118
+ email: 'bob@example.com',
119
+ party_size: 6,
120
+ check_in_time: Date.now(),
121
+ table_preference: 'booth',
122
+ status: 'waiting',
123
+ quoted_wait_time: 45,
124
+ customer_id: 3,
125
+ },
126
+ ]
127
+
128
+ // Create the waitlist entries
129
+ await bulkStore(requests)
130
+
131
+ // Fetch seated entries between dates
132
+ const seatedEntries = await fetchSeatedBetweenDates(startDate, endDate)
133
+ expect(seatedEntries).toBeDefined()
134
+ expect(seatedEntries.length).toBe(2)
135
+ expect(seatedEntries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('John Doe')
136
+ expect(seatedEntries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('Jane Smith')
137
+ })
138
+
139
+ it('should fetch all waitlist entries with waiting status', async () => {
140
+ // Create test waitlist entries with different statuses
141
+ const requests = [
142
+ {
143
+ name: 'John Doe',
144
+ email: 'john@example.com',
145
+ party_size: 4,
146
+ check_in_time: Date.now(),
147
+ table_preference: 'indoor',
148
+ status: 'waiting',
149
+ quoted_wait_time: 30,
150
+ customer_id: 1,
151
+ },
152
+ {
153
+ name: 'Jane Smith',
154
+ email: 'jane@example.com',
155
+ party_size: 2,
156
+ check_in_time: Date.now(),
157
+ table_preference: 'bar',
158
+ status: 'waiting',
159
+ quoted_wait_time: 15,
160
+ customer_id: 2,
161
+ },
162
+ {
163
+ name: 'Bob Wilson',
164
+ email: 'bob@example.com',
165
+ party_size: 6,
166
+ check_in_time: Date.now(),
167
+ table_preference: 'booth',
168
+ status: 'seated',
169
+ quoted_wait_time: 45,
170
+ customer_id: 3,
171
+ },
172
+ ]
173
+
174
+ // Create the waitlist entries
175
+ await bulkStore(requests)
176
+
177
+ // Fetch entries with waiting status
178
+ const waitingEntries = await fetchWaiting()
179
+ expect(waitingEntries).toBeDefined()
180
+ expect(waitingEntries.length).toBe(2)
181
+ expect(waitingEntries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('John Doe')
182
+ expect(waitingEntries.map((w: WaitlistRestaurantJsonResponse) => w.name)).toContain('Jane Smith')
183
+ })
184
+
185
+ it('should fetch waiting entries with quoted wait times', async () => {
186
+ // Create test waitlist entries with different wait times and statuses
187
+ const requests = [
188
+ {
189
+ name: 'John Doe',
190
+ email: 'john@example.com',
191
+ party_size: 4,
192
+ check_in_time: Date.now(),
193
+ table_preference: 'indoor',
194
+ status: 'waiting',
195
+ quoted_wait_time: 30,
196
+ customer_id: 1,
197
+ },
198
+ {
199
+ name: 'Jane Smith',
200
+ email: 'jane@example.com',
201
+ party_size: 2,
202
+ check_in_time: Date.now(),
203
+ table_preference: 'bar',
204
+ status: 'waiting',
205
+ quoted_wait_time: 15,
206
+ customer_id: 2,
207
+ },
208
+ {
209
+ name: 'Bob Wilson',
210
+ email: 'bob@example.com',
211
+ party_size: 6,
212
+ check_in_time: Date.now(),
213
+ table_preference: 'booth',
214
+ status: 'seated',
215
+ quoted_wait_time: 45,
216
+ customer_id: 3,
217
+ },
218
+ ]
219
+
220
+ // Create the waitlist entries
221
+ await bulkStore(requests)
222
+
223
+ // Fetch waiting entries with quoted times
224
+ const result = await fetchWaitingWithQuotedTimes()
225
+
226
+ // Verify the results
227
+ expect(result).toBeDefined()
228
+ expect(result.entries).toBeDefined()
229
+ expect(result.entries.length).toBe(2) // Only waiting entries
230
+ expect(result.totalQuotedWaitTime).toBe(45) // 30 + 15
231
+ expect(result.averageQuotedWaitTime).toBe(22.5) // (30 + 15) / 2
232
+
233
+ // Verify individual entries
234
+ expect(result.entries.map(e => e.name)).toContain('John Doe')
235
+ expect(result.entries.map(e => e.name)).toContain('Jane Smith')
236
+ expect(result.entries.map(e => e.quoted_wait_time)).toContain(30)
237
+ expect(result.entries.map(e => e.quoted_wait_time)).toContain(15)
238
+ })
239
+
240
+ it('should handle empty waiting list for quoted times', async () => {
241
+ // Create only seated entries
242
+ const requests = [
243
+ {
244
+ name: 'John Doe',
245
+ email: 'john@example.com',
246
+ party_size: 4,
247
+ check_in_time: Date.now(),
248
+ table_preference: 'indoor',
249
+ status: 'seated',
250
+ quoted_wait_time: 30,
251
+ customer_id: 1,
252
+ },
253
+ {
254
+ name: 'Jane Smith',
255
+ email: 'jane@example.com',
256
+ party_size: 2,
257
+ check_in_time: Date.now(),
258
+ table_preference: 'bar',
259
+ status: 'seated',
260
+ quoted_wait_time: 15,
261
+ customer_id: 2,
262
+ },
263
+ ]
264
+
265
+ // Create the waitlist entries
266
+ await bulkStore(requests)
267
+
268
+ // Fetch waiting entries with quoted times
269
+ const result = await fetchWaitingWithQuotedTimes()
270
+
271
+ // Verify the results
272
+ expect(result).toBeDefined()
273
+ expect(result.entries).toBeDefined()
274
+ expect(result.entries.length).toBe(0)
275
+ expect(result.totalQuotedWaitTime).toBe(0)
276
+ expect(result.averageQuotedWaitTime).toBe(0)
277
+ })
278
+
279
+ it('should fetch waiting entries with party size calculations', async () => {
280
+ // Create test waitlist entries with different party sizes and statuses
281
+ const requests = [
282
+ {
283
+ name: 'John Doe',
284
+ email: 'john@example.com',
285
+ party_size: 4,
286
+ check_in_time: Date.now(),
287
+ table_preference: 'indoor',
288
+ status: 'waiting',
289
+ quoted_wait_time: 30,
290
+ customer_id: 1,
291
+ },
292
+ {
293
+ name: 'Jane Smith',
294
+ email: 'jane@example.com',
295
+ party_size: 4,
296
+ check_in_time: Date.now(),
297
+ table_preference: 'bar',
298
+ status: 'waiting',
299
+ quoted_wait_time: 15,
300
+ customer_id: 2,
301
+ },
302
+ {
303
+ name: 'Bob Wilson',
304
+ email: 'bob@example.com',
305
+ party_size: 2,
306
+ check_in_time: Date.now(),
307
+ table_preference: 'booth',
308
+ status: 'waiting',
309
+ quoted_wait_time: 45,
310
+ customer_id: 3,
311
+ },
312
+ {
313
+ name: 'Alice Brown',
314
+ email: 'alice@example.com',
315
+ party_size: 6,
316
+ check_in_time: Date.now(),
317
+ table_preference: 'indoor',
318
+ status: 'seated',
319
+ quoted_wait_time: 60,
320
+ customer_id: 4,
321
+ },
322
+ ]
323
+
324
+ // Create the waitlist entries
325
+ await bulkStore(requests)
326
+
327
+ // Fetch waiting entries with party sizes
328
+ const result = await fetchWaitingWithPartySizes()
329
+
330
+ // Verify the results
331
+ expect(result).toBeDefined()
332
+ expect(result.entries).toBeDefined()
333
+ expect(result.entries.length).toBe(3) // Only waiting entries
334
+ expect(result.totalPartySize).toBe(10) // 4 + 4 + 2
335
+ expect(result.averagePartySize).toBeCloseTo(3.33, 2) // 10 / 3
336
+
337
+ // Verify party size breakdown
338
+ expect(result.partySizeBreakdown).toBeDefined()
339
+ expect(Object.keys(result.partySizeBreakdown).length).toBe(2) // 2 different party sizes
340
+ expect(result.partySizeBreakdown[4]).toBe(2) // Two parties of 4
341
+ expect(result.partySizeBreakdown[2]).toBe(1) // One party of 2
342
+
343
+ // Verify individual entries
344
+ expect(result.entries.map(e => e.name)).toContain('John Doe')
345
+ expect(result.entries.map(e => e.name)).toContain('Jane Smith')
346
+ expect(result.entries.map(e => e.name)).toContain('Bob Wilson')
347
+ })
348
+
349
+ it('should handle empty waiting list for party sizes', async () => {
350
+ // Create only seated entries
351
+ const requests = [
352
+ {
353
+ name: 'John Doe',
354
+ email: 'john@example.com',
355
+ party_size: 4,
356
+ check_in_time: Date.now(),
357
+ table_preference: 'indoor',
358
+ status: 'seated',
359
+ quoted_wait_time: 30,
360
+ customer_id: 1,
361
+ },
362
+ {
363
+ name: 'Jane Smith',
364
+ email: 'jane@example.com',
365
+ party_size: 2,
366
+ check_in_time: Date.now(),
367
+ table_preference: 'bar',
368
+ status: 'seated',
369
+ quoted_wait_time: 15,
370
+ customer_id: 2,
371
+ },
372
+ ]
373
+
374
+ // Create the waitlist entries
375
+ await bulkStore(requests)
376
+
377
+ // Fetch waiting entries with party sizes
378
+ const result = await fetchWaitingWithPartySizes()
379
+
380
+ // Verify the results
381
+ expect(result).toBeDefined()
382
+ expect(result.entries).toBeDefined()
383
+ expect(result.entries.length).toBe(0)
384
+ expect(result.totalPartySize).toBe(0)
385
+ expect(result.averagePartySize).toBe(0)
386
+ expect(result.partySizeBreakdown).toEqual({})
387
+ })
388
+
389
+ it('should fetch tables turned statistics for today', async () => {
390
+ // Create test dates using today
391
+ const today = new Date()
392
+ const startOfDay = new Date(today)
393
+ startOfDay.setHours(0, 0, 0, 0)
394
+ const endOfDay = new Date(today)
395
+ endOfDay.setHours(23, 59, 59, 999)
396
+
397
+ // Create test waitlist entries with different statuses and table preferences
398
+ const requests = [
399
+ {
400
+ name: 'John Doe',
401
+ email: 'john@example.com',
402
+ party_size: 4,
403
+ check_in_time: startOfDay.getTime(),
404
+ table_preference: 'indoor',
405
+ status: 'seated',
406
+ quoted_wait_time: 30,
407
+ customer_id: 1,
408
+ },
409
+ {
410
+ name: 'Jane Smith',
411
+ email: 'jane@example.com',
412
+ party_size: 2,
413
+ check_in_time: endOfDay.getTime(),
414
+ table_preference: 'bar',
415
+ status: 'seated',
416
+ quoted_wait_time: 15,
417
+ customer_id: 2,
418
+ },
419
+ {
420
+ name: 'Bob Wilson',
421
+ email: 'bob@example.com',
422
+ party_size: 6,
423
+ check_in_time: Date.now(),
424
+ table_preference: 'booth',
425
+ status: 'seated',
426
+ quoted_wait_time: 45,
427
+ customer_id: 3,
428
+ },
429
+ {
430
+ name: 'Alice Brown',
431
+ email: 'alice@example.com',
432
+ party_size: 3,
433
+ check_in_time: Date.now(),
434
+ table_preference: 'indoor',
435
+ status: 'waiting',
436
+ quoted_wait_time: 20,
437
+ customer_id: 4,
438
+ },
439
+ ]
440
+
441
+ // Create the waitlist entries
442
+ await bulkStore(requests)
443
+
444
+ // Fetch tables turned statistics
445
+ const stats = await fetchTablesTurnedToday()
446
+
447
+ // Verify the results
448
+ expect(stats).toBeDefined()
449
+ expect(stats.totalTablesTurned).toBe(3) // 3 seated entries
450
+ expect(stats.totalCustomersSeated).toBe(12) // 4 + 2 + 6
451
+ expect(stats.averagePartySize).toBe(4) // 12 / 3
452
+
453
+ // Verify table preference breakdown
454
+ expect(stats.breakdownByTablePreference).toBeDefined()
455
+ expect(Object.keys(stats.breakdownByTablePreference).length).toBe(3) // indoor, bar, booth
456
+ expect(stats.breakdownByTablePreference.indoor).toBe(1)
457
+ expect(stats.breakdownByTablePreference.bar).toBe(1)
458
+ expect(stats.breakdownByTablePreference.booth).toBe(1)
459
+
460
+ // Verify party size breakdown
461
+ expect(stats.breakdownByPartySize).toBeDefined()
462
+ expect(Object.keys(stats.breakdownByPartySize).length).toBe(3) // 2, 3, 4, 6
463
+ expect(stats.breakdownByPartySize[2]).toBe(1)
464
+ expect(stats.breakdownByPartySize[4]).toBe(1)
465
+ expect(stats.breakdownByPartySize[6]).toBe(1)
466
+ })
467
+
468
+ it('should handle no tables turned today', async () => {
469
+ // Create test entries with different dates
470
+ const today = new Date()
471
+ const yesterday = new Date(today)
472
+ yesterday.setDate(yesterday.getDate() - 1)
473
+
474
+ const requests = [
475
+ {
476
+ name: 'John Doe',
477
+ email: 'john@example.com',
478
+ party_size: 4,
479
+ check_in_time: yesterday.getTime(),
480
+ table_preference: 'indoor',
481
+ status: 'seated',
482
+ quoted_wait_time: 30,
483
+ customer_id: 1,
484
+ },
485
+ {
486
+ name: 'Jane Smith',
487
+ email: 'jane@example.com',
488
+ party_size: 2,
489
+ check_in_time: yesterday.getTime(),
490
+ table_preference: 'bar',
491
+ status: 'seated',
492
+ quoted_wait_time: 15,
493
+ customer_id: 2,
494
+ },
495
+ {
496
+ name: 'Bob Wilson',
497
+ email: 'bob@example.com',
498
+ party_size: 6,
499
+ check_in_time: Date.now(),
500
+ table_preference: 'booth',
501
+ status: 'waiting',
502
+ quoted_wait_time: 45,
503
+ customer_id: 3,
504
+ },
505
+ ]
506
+
507
+ // Create the waitlist entries
508
+ await bulkStore(requests)
509
+
510
+ // Fetch tables turned statistics
511
+ const stats = await fetchTablesTurnedToday()
512
+
513
+ // Verify the results
514
+ expect(stats).toBeDefined()
515
+ expect(stats.totalTablesTurned).toBe(0)
516
+ expect(stats.totalCustomersSeated).toBe(0)
517
+ expect(stats.averagePartySize).toBe(0)
518
+ expect(stats.breakdownByTablePreference).toEqual({})
519
+ expect(stats.breakdownByPartySize).toEqual({})
520
+ })
521
+ })
522
+ })
@@ -0,0 +1,165 @@
1
+ import { beforeEach, describe, expect, it } from 'bun:test'
2
+ import { refreshDatabase } from './setup'
3
+ import { bulkDestroy } from '../products/reviews/destroy'
4
+ import { fetchApprovedByProductId, fetchByProductId, fetchByUserId, fetchMostHelpfulByProductId } from '../products/reviews/fetch'
5
+ import { store } from '../products/reviews/store'
6
+
7
+ beforeEach(async () => {
8
+ await refreshDatabase()
9
+ })
10
+
11
+ describe('Product Review Module', () => {
12
+ describe('fetch methods', () => {
13
+ it('should fetch reviews by product ID', async () => {
14
+ // Create several reviews for the same product
15
+ const productId = 2
16
+
17
+ // Create first review
18
+ const request1 = {
19
+ product_id: productId,
20
+ customer_id: 1,
21
+ rating: 5,
22
+ title: 'First review',
23
+ content: 'First review content',
24
+ is_approved: true,
25
+ }
26
+
27
+ await store(request1)
28
+
29
+ // Create second review
30
+ const request2 = {
31
+ product_id: productId,
32
+ customer_id: 2,
33
+ rating: 4,
34
+ title: 'Second review',
35
+ content: 'Second review content',
36
+ is_approved: true,
37
+ }
38
+
39
+ await store(request2)
40
+
41
+ // Now fetch reviews by product ID
42
+ const result = await fetchByProductId(productId)
43
+
44
+ expect(result).toBeDefined()
45
+ expect(result.length).toBe(2)
46
+ expect(result[0].product_id).toBe(productId)
47
+ expect(result[1].product_id).toBe(productId)
48
+ })
49
+
50
+ it('should fetch reviews by user ID', async () => {
51
+ // Create several reviews for the same user
52
+ const customerId = 3
53
+
54
+ // Create first review
55
+ const request1 = {
56
+ product_id: 1,
57
+ customer_id: customerId,
58
+ rating: 5,
59
+ title: 'First user review',
60
+ content: 'First user review content',
61
+ }
62
+ await store(request1)
63
+
64
+ // Create second review
65
+ const request2 = {
66
+ product_id: 2,
67
+ customer_id: customerId,
68
+ rating: 3,
69
+ title: 'Second user review',
70
+ content: 'Second user review content',
71
+ }
72
+ await store(request2)
73
+
74
+ // Now fetch reviews by user ID
75
+ const result = await fetchByUserId(customerId)
76
+
77
+ expect(result).toBeDefined()
78
+ expect(result.length).toBe(2)
79
+ expect(result[0].customer_id).toBe(customerId)
80
+ expect(result[1].customer_id).toBe(customerId)
81
+ })
82
+
83
+ it('should fetch approved reviews for a product', async () => {
84
+ const productId = 3
85
+
86
+ // Create approved review
87
+ const request1 = {
88
+ product_id: productId,
89
+ customer_id: 1,
90
+ rating: 5,
91
+ title: 'Approved review',
92
+ content: 'Approved review content',
93
+ is_approved: true,
94
+ }
95
+
96
+ await store(request1)
97
+
98
+ // Create unapproved review
99
+ const request2 = {
100
+ product_id: productId,
101
+ customer_id: 2,
102
+ rating: 2,
103
+ title: 'Unapproved review',
104
+ content: 'Unapproved review content',
105
+ is_approved: false,
106
+ }
107
+
108
+ await store(request2)
109
+
110
+ // Now fetch only approved reviews
111
+ const result = await fetchApprovedByProductId(productId)
112
+
113
+ expect(result).toBeDefined()
114
+ expect(result.length).toBe(1)
115
+ expect(Boolean(result[0].is_approved)).toBe(true)
116
+ expect(result[0].title).toBe('Approved review')
117
+ })
118
+
119
+ it('should fetch most helpful reviews', async () => {
120
+ const productId = 5
121
+
122
+ // Create review with many helpful votes
123
+ const request1 = {
124
+ product_id: productId,
125
+ customer_id: 1,
126
+ rating: 5,
127
+ title: 'Most helpful review',
128
+ content: 'Very helpful content',
129
+ is_approved: true,
130
+ helpful_votes: 10,
131
+ }
132
+
133
+ await store(request1)
134
+
135
+ // Create review with fewer helpful votes
136
+ const request2 = {
137
+ product_id: productId,
138
+ customer_id: 2,
139
+ rating: 4,
140
+ title: 'Less helpful review',
141
+ content: 'Still helpful content',
142
+ is_approved: true,
143
+ helpful_votes: 5,
144
+ }
145
+
146
+ await store(request2)
147
+
148
+ // Now fetch most helpful reviews
149
+ const helpfulReviews = await fetchMostHelpfulByProductId(productId)
150
+
151
+ expect(helpfulReviews).toBeDefined()
152
+ expect(helpfulReviews.length).toBe(2)
153
+ expect(helpfulReviews[0].helpful_votes).toBe(10)
154
+ expect(helpfulReviews[1].helpful_votes).toBe(5)
155
+ })
156
+ })
157
+
158
+ describe('destroy', () => {
159
+ it('should return 0 when trying to delete an empty array of reviews', async () => {
160
+ // Try to delete with an empty array
161
+ const deletedCount = await bulkDestroy([])
162
+ expect(deletedCount).toBe(0)
163
+ })
164
+ })
165
+ })