@tellescope/sdk 1.245.1 → 1.246.2

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 (67) hide show
  1. package/lib/cjs/tests/api_tests/account_switcher.test.d.ts +6 -0
  2. package/lib/cjs/tests/api_tests/account_switcher.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/account_switcher.test.js +445 -0
  4. package/lib/cjs/tests/api_tests/account_switcher.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/beluga_pharmacy_mappings.test.d.ts +6 -0
  6. package/lib/cjs/tests/api_tests/beluga_pharmacy_mappings.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/beluga_pharmacy_mappings.test.js +357 -0
  8. package/lib/cjs/tests/api_tests/beluga_pharmacy_mappings.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/calendar_event_limits.test.d.ts.map +1 -1
  10. package/lib/cjs/tests/api_tests/calendar_event_limits.test.js +163 -0
  11. package/lib/cjs/tests/api_tests/calendar_event_limits.test.js.map +1 -1
  12. package/lib/cjs/tests/api_tests/calendar_events_bulk_update.test.d.ts +5 -0
  13. package/lib/cjs/tests/api_tests/calendar_events_bulk_update.test.d.ts.map +1 -0
  14. package/lib/cjs/tests/api_tests/calendar_events_bulk_update.test.js +483 -0
  15. package/lib/cjs/tests/api_tests/calendar_events_bulk_update.test.js.map +1 -0
  16. package/lib/cjs/tests/api_tests/email_utils.test.d.ts +2 -0
  17. package/lib/cjs/tests/api_tests/email_utils.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/email_utils.test.js +141 -0
  19. package/lib/cjs/tests/api_tests/email_utils.test.js.map +1 -0
  20. package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.d.ts +6 -0
  21. package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.d.ts.map +1 -0
  22. package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.js +268 -0
  23. package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.js.map +1 -0
  24. package/lib/cjs/tests/api_tests/time_tracks.test.d.ts +20 -0
  25. package/lib/cjs/tests/api_tests/time_tracks.test.d.ts.map +1 -1
  26. package/lib/cjs/tests/api_tests/time_tracks.test.js +692 -20
  27. package/lib/cjs/tests/api_tests/time_tracks.test.js.map +1 -1
  28. package/lib/cjs/tests/tests.d.ts.map +1 -1
  29. package/lib/cjs/tests/tests.js +157 -122
  30. package/lib/cjs/tests/tests.js.map +1 -1
  31. package/lib/esm/tests/api_tests/account_switcher.test.d.ts +6 -0
  32. package/lib/esm/tests/api_tests/account_switcher.test.d.ts.map +1 -0
  33. package/lib/esm/tests/api_tests/account_switcher.test.js +438 -0
  34. package/lib/esm/tests/api_tests/account_switcher.test.js.map +1 -0
  35. package/lib/esm/tests/api_tests/beluga_pharmacy_mappings.test.d.ts +6 -0
  36. package/lib/esm/tests/api_tests/beluga_pharmacy_mappings.test.d.ts.map +1 -0
  37. package/lib/esm/tests/api_tests/beluga_pharmacy_mappings.test.js +353 -0
  38. package/lib/esm/tests/api_tests/beluga_pharmacy_mappings.test.js.map +1 -0
  39. package/lib/esm/tests/api_tests/calendar_event_limits.test.d.ts.map +1 -1
  40. package/lib/esm/tests/api_tests/calendar_event_limits.test.js +163 -0
  41. package/lib/esm/tests/api_tests/calendar_event_limits.test.js.map +1 -1
  42. package/lib/esm/tests/api_tests/calendar_events_bulk_update.test.d.ts +5 -0
  43. package/lib/esm/tests/api_tests/calendar_events_bulk_update.test.d.ts.map +1 -0
  44. package/lib/esm/tests/api_tests/calendar_events_bulk_update.test.js +479 -0
  45. package/lib/esm/tests/api_tests/calendar_events_bulk_update.test.js.map +1 -0
  46. package/lib/esm/tests/api_tests/email_utils.test.d.ts +2 -0
  47. package/lib/esm/tests/api_tests/email_utils.test.d.ts.map +1 -0
  48. package/lib/esm/tests/api_tests/email_utils.test.js +137 -0
  49. package/lib/esm/tests/api_tests/email_utils.test.js.map +1 -0
  50. package/lib/esm/tests/api_tests/organization_settings_duplicates.test.d.ts +6 -0
  51. package/lib/esm/tests/api_tests/organization_settings_duplicates.test.d.ts.map +1 -0
  52. package/lib/esm/tests/api_tests/organization_settings_duplicates.test.js +264 -0
  53. package/lib/esm/tests/api_tests/organization_settings_duplicates.test.js.map +1 -0
  54. package/lib/esm/tests/api_tests/time_tracks.test.d.ts +20 -0
  55. package/lib/esm/tests/api_tests/time_tracks.test.d.ts.map +1 -1
  56. package/lib/esm/tests/api_tests/time_tracks.test.js +687 -20
  57. package/lib/esm/tests/api_tests/time_tracks.test.js.map +1 -1
  58. package/lib/esm/tests/tests.d.ts.map +1 -1
  59. package/lib/esm/tests/tests.js +158 -123
  60. package/lib/esm/tests/tests.js.map +1 -1
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +10 -10
  63. package/src/tests/api_tests/beluga_pharmacy_mappings.test.ts +351 -0
  64. package/src/tests/api_tests/calendar_event_limits.test.ts +195 -0
  65. package/src/tests/api_tests/time_tracks.test.ts +542 -16
  66. package/src/tests/tests.ts +34 -5
  67. package/test_generated.pdf +0 -0
@@ -0,0 +1,351 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ log_header,
7
+ } from "@tellescope/testing"
8
+ import { setup_tests } from "../setup"
9
+ import { evaluate_conditional_logic } from "@tellescope/utilities"
10
+ import { CompoundFilter, BelugaPharmacyMapping } from "@tellescope/types-models"
11
+
12
+ const host = process.env.API_URL || 'http://localhost:8080' as const
13
+
14
+ // Helper mimicking resolve_beluga_pharmacy_mapping evaluator
15
+ const evaluate_mapping_conditions = (
16
+ conditions: CompoundFilter<string>,
17
+ responses: { externalId?: string, value?: string }[]
18
+ ) => evaluate_conditional_logic(conditions, (key, value) => {
19
+ const responseValue = responses.find(r => r.externalId === key)?.value
20
+
21
+ if (typeof value === 'string') {
22
+ return responseValue === value
23
+ }
24
+ if (typeof value === 'object' && value !== null) {
25
+ const operator = Object.keys(value)[0]
26
+ const operand = Object.values(value)[0]
27
+
28
+ if (operator === '$contains') {
29
+ return typeof responseValue === 'string' && responseValue.includes(String(operand))
30
+ }
31
+ if (operator === '$doesNotContain') {
32
+ return typeof responseValue === 'string' && !responseValue.includes(String(operand))
33
+ }
34
+ if (operator === '$exists') {
35
+ return operand ? responseValue !== undefined : responseValue === undefined
36
+ }
37
+ if (operator === '$ne') {
38
+ return responseValue !== String(operand)
39
+ }
40
+ }
41
+
42
+ return false
43
+ })
44
+
45
+ // Helper to resolve first matching mapping
46
+ const resolve_mapping = (
47
+ mappings: BelugaPharmacyMapping[],
48
+ responses: { externalId?: string, value?: string }[]
49
+ ): BelugaPharmacyMapping | undefined => {
50
+ for (const mapping of mappings) {
51
+ if (!mapping.conditions) continue
52
+ if (evaluate_mapping_conditions(mapping.conditions, responses)) {
53
+ return mapping
54
+ }
55
+ }
56
+ return undefined
57
+ }
58
+
59
+ export const beluga_pharmacy_mappings_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
60
+ log_header("Beluga Pharmacy Mappings Tests")
61
+
62
+ // --- 6a. CRUD Tests ---
63
+
64
+ let testFormId: string | undefined
65
+
66
+ try {
67
+ await async_test(
68
+ "Create form with belugaPharmacyMappings",
69
+ async () => {
70
+ const mappings: BelugaPharmacyMapping[] = [
71
+ {
72
+ pharmacyId: "pharmacy-001",
73
+ patientPreference: JSON.stringify([{ name: "Med A", strength: "10mg", quantity: "30", refills: "3", daysSupply: "30", sig: "Take once daily", dispenseUnit: "Tablet", medId: "med-001" }]),
74
+ conditions: { $and: [{ condition: { "state": "CA" } }, { condition: { "med_type": "weightLoss" } }] },
75
+ },
76
+ ]
77
+
78
+ const form = await sdk.api.forms.createOne({
79
+ title: 'Beluga Pharmacy Mappings Test Form',
80
+ belugaPharmacyMappings: mappings,
81
+ })
82
+ testFormId = form.id
83
+
84
+ const fetched = await sdk.api.forms.getOne(form.id)
85
+ return (
86
+ fetched.belugaPharmacyMappings?.length === 1
87
+ && fetched.belugaPharmacyMappings[0].pharmacyId === "pharmacy-001"
88
+ && !!fetched.belugaPharmacyMappings[0].conditions?.$and
89
+ )
90
+ },
91
+ { expectedResult: true }
92
+ )
93
+
94
+ await async_test(
95
+ "Update belugaPharmacyMappings on existing form",
96
+ async () => {
97
+ if (!testFormId) throw new Error("No test form")
98
+
99
+ const updatedMappings: BelugaPharmacyMapping[] = [
100
+ {
101
+ pharmacyId: "pharmacy-002",
102
+ patientPreference: JSON.stringify([{ name: "Med B", strength: "20mg", quantity: "60", refills: "2", daysSupply: "30", sig: "Take twice daily", dispenseUnit: "Capsule", medId: "med-002" }]),
103
+ conditions: { condition: { "state": "NY" } },
104
+ },
105
+ {
106
+ pharmacyId: "pharmacy-003",
107
+ patientPreference: "[]",
108
+ conditions: { $or: [{ condition: { "state": "TX" } }, { condition: { "state": "FL" } }] },
109
+ },
110
+ ]
111
+
112
+ await sdk.api.forms.updateOne(testFormId, { belugaPharmacyMappings: updatedMappings }, { replaceObjectFields: true })
113
+ const fetched = await sdk.api.forms.getOne(testFormId)
114
+ return (
115
+ fetched.belugaPharmacyMappings?.length === 2
116
+ && fetched.belugaPharmacyMappings[0].pharmacyId === "pharmacy-002"
117
+ && fetched.belugaPharmacyMappings[1].pharmacyId === "pharmacy-003"
118
+ )
119
+ },
120
+ { expectedResult: true }
121
+ )
122
+
123
+ await async_test(
124
+ "Save with nested CompoundFilter structure",
125
+ async () => {
126
+ if (!testFormId) throw new Error("No test form")
127
+
128
+ const nestedMappings: BelugaPharmacyMapping[] = [
129
+ {
130
+ pharmacyId: "pharmacy-nested",
131
+ patientPreference: "[]",
132
+ conditions: {
133
+ $and: [
134
+ { $or: [{ condition: { "state": "CA" } }, { condition: { "state": "NY" } }] },
135
+ { condition: { "med_type": "weightLoss" } },
136
+ ]
137
+ },
138
+ },
139
+ ]
140
+
141
+ await sdk.api.forms.updateOne(testFormId, { belugaPharmacyMappings: nestedMappings }, { replaceObjectFields: true })
142
+ const fetched = await sdk.api.forms.getOne(testFormId)
143
+ return (
144
+ fetched.belugaPharmacyMappings?.length === 1
145
+ && fetched.belugaPharmacyMappings[0].pharmacyId === "pharmacy-nested"
146
+ && !!fetched.belugaPharmacyMappings[0].conditions?.$and
147
+ )
148
+ },
149
+ { expectedResult: true }
150
+ )
151
+
152
+ // --- 6b. Conditional logic evaluation tests ---
153
+
154
+ await async_test(
155
+ "Simple equality: condition matches response",
156
+ async () => {
157
+ return evaluate_mapping_conditions(
158
+ { condition: { "state": "CA" } },
159
+ [{ externalId: "state", value: "CA" }]
160
+ )
161
+ },
162
+ { expectedResult: true }
163
+ )
164
+
165
+ await async_test(
166
+ "Simple equality miss: condition does not match response",
167
+ async () => {
168
+ return evaluate_mapping_conditions(
169
+ { condition: { "state": "CA" } },
170
+ [{ externalId: "state", value: "NY" }]
171
+ )
172
+ },
173
+ { expectedResult: false }
174
+ )
175
+
176
+ await async_test(
177
+ "$and: two conditions, both match",
178
+ async () => {
179
+ return evaluate_mapping_conditions(
180
+ { $and: [{ condition: { "state": "CA" } }, { condition: { "med_type": "weightLoss" } }] },
181
+ [{ externalId: "state", value: "CA" }, { externalId: "med_type", value: "weightLoss" }]
182
+ )
183
+ },
184
+ { expectedResult: true }
185
+ )
186
+
187
+ await async_test(
188
+ "$and: two conditions, one misses",
189
+ async () => {
190
+ return evaluate_mapping_conditions(
191
+ { $and: [{ condition: { "state": "CA" } }, { condition: { "med_type": "weightLoss" } }] },
192
+ [{ externalId: "state", value: "CA" }, { externalId: "med_type", value: "skincare" }]
193
+ )
194
+ },
195
+ { expectedResult: false }
196
+ )
197
+
198
+ await async_test(
199
+ "$or: two conditions, one matches",
200
+ async () => {
201
+ return evaluate_mapping_conditions(
202
+ { $or: [{ condition: { "state": "CA" } }, { condition: { "state": "NY" } }] },
203
+ [{ externalId: "state", value: "NY" }]
204
+ )
205
+ },
206
+ { expectedResult: true }
207
+ )
208
+
209
+ await async_test(
210
+ "$or: two conditions, neither matches",
211
+ async () => {
212
+ return evaluate_mapping_conditions(
213
+ { $or: [{ condition: { "state": "CA" } }, { condition: { "state": "NY" } }] },
214
+ [{ externalId: "state", value: "TX" }]
215
+ )
216
+ },
217
+ { expectedResult: false }
218
+ )
219
+
220
+ await async_test(
221
+ "$contains: value includes substring",
222
+ async () => {
223
+ return evaluate_mapping_conditions(
224
+ { condition: { "meds": { $contains: "GLP" } as any } },
225
+ [{ externalId: "meds", value: "GLP-1 agonist" }]
226
+ )
227
+ },
228
+ { expectedResult: true }
229
+ )
230
+
231
+ await async_test(
232
+ "$ne: value does not equal",
233
+ async () => {
234
+ return evaluate_mapping_conditions(
235
+ { condition: { "state": { $ne: "CA" } as any } },
236
+ [{ externalId: "state", value: "NY" }]
237
+ )
238
+ },
239
+ { expectedResult: true }
240
+ )
241
+
242
+ await async_test(
243
+ "$exists: field present",
244
+ async () => {
245
+ return evaluate_mapping_conditions(
246
+ { condition: { "pharmacyOverride": { $exists: true } as any } },
247
+ [{ externalId: "pharmacyOverride", value: "some-value" }]
248
+ )
249
+ },
250
+ { expectedResult: true }
251
+ )
252
+
253
+ await async_test(
254
+ "$exists: field absent",
255
+ async () => {
256
+ return evaluate_mapping_conditions(
257
+ { condition: { "pharmacyOverride": { $exists: true } as any } },
258
+ [{ externalId: "other_field", value: "some-value" }]
259
+ )
260
+ },
261
+ { expectedResult: false }
262
+ )
263
+
264
+ await async_test(
265
+ "Nested compound: $and with $or",
266
+ async () => {
267
+ return evaluate_mapping_conditions(
268
+ {
269
+ $and: [
270
+ { $or: [{ condition: { "state": "CA" } }, { condition: { "state": "NY" } }] },
271
+ { condition: { "med_type": "weightLoss" } },
272
+ ]
273
+ },
274
+ [{ externalId: "state", value: "NY" }, { externalId: "med_type", value: "weightLoss" }]
275
+ )
276
+ },
277
+ { expectedResult: true }
278
+ )
279
+
280
+ await async_test(
281
+ "First-match-wins: multiple mappings, first matching returned",
282
+ async () => {
283
+ const mappings: BelugaPharmacyMapping[] = [
284
+ {
285
+ pharmacyId: "pharmacy-first",
286
+ patientPreference: "[]",
287
+ conditions: { condition: { "state": "CA" } },
288
+ },
289
+ {
290
+ pharmacyId: "pharmacy-second",
291
+ patientPreference: "[]",
292
+ conditions: { condition: { "state": "CA" } },
293
+ },
294
+ ]
295
+
296
+ const result = resolve_mapping(
297
+ mappings,
298
+ [{ externalId: "state", value: "CA" }]
299
+ )
300
+ return result?.pharmacyId === "pharmacy-first"
301
+ },
302
+ { expectedResult: true }
303
+ )
304
+
305
+ await async_test(
306
+ "No match: no mappings match, returns undefined",
307
+ async () => {
308
+ const mappings: BelugaPharmacyMapping[] = [
309
+ {
310
+ pharmacyId: "pharmacy-ca",
311
+ patientPreference: "[]",
312
+ conditions: { condition: { "state": "CA" } },
313
+ },
314
+ ]
315
+
316
+ const result = resolve_mapping(
317
+ mappings,
318
+ [{ externalId: "state", value: "TX" }]
319
+ )
320
+ return result === undefined
321
+ },
322
+ { expectedResult: true }
323
+ )
324
+ } finally {
325
+ // Cleanup
326
+ if (testFormId) {
327
+ await sdk.api.forms.deleteOne(testFormId).catch(console.error)
328
+ }
329
+ }
330
+ }
331
+
332
+ if (require.main === module) {
333
+ console.log(`Using API URL: ${host}`)
334
+ const sdk = new Session({ host })
335
+ const sdkNonAdmin = new Session({ host })
336
+
337
+ const runTests = async () => {
338
+ await setup_tests(sdk, sdkNonAdmin)
339
+ await beluga_pharmacy_mappings_tests({ sdk, sdkNonAdmin })
340
+ }
341
+
342
+ runTests()
343
+ .then(() => {
344
+ console.log("Beluga pharmacy mappings test suite completed successfully")
345
+ process.exit(0)
346
+ })
347
+ .catch((error) => {
348
+ console.error("Beluga pharmacy mappings test suite failed:", error)
349
+ process.exit(1)
350
+ })
351
+ }
@@ -685,6 +685,201 @@ export const calendar_event_limits_unit_tests = () => {
685
685
  }
686
686
  })
687
687
 
688
+ // === otherTemplateIds (OR logic) tests ===
689
+
690
+ const TEMPLATE_B = "templateB456"
691
+ const TEMPLATE_C = "templateC789"
692
+
693
+ // Test 21: otherTemplateIds - booking a secondary template should count toward the limit
694
+ tests.push({
695
+ name: 'otherTemplateIds - secondary template counts toward limit',
696
+ fn: () => {
697
+ const oct1 = new Date('2025-10-01T18:00:00.000Z').getTime()
698
+
699
+ const limits: CalendarEventLimit[] = [{
700
+ templateId: TEMPLATE_ID,
701
+ otherTemplateIds: [TEMPLATE_B],
702
+ period: 1,
703
+ limit: 1,
704
+ }]
705
+
706
+ const existingEvents = [
707
+ createEvent(oct1, TEMPLATE_ID), // 1 event of primary template
708
+ ]
709
+
710
+ // Booking template B should be blocked because the shared limit is reached
711
+ const result = slot_violates_calendar_event_limits({
712
+ slotStartTimeInMS: oct1,
713
+ templateId: TEMPLATE_B,
714
+ userId: USER_ID,
715
+ calendarEventLimits: limits,
716
+ existingEvents,
717
+ timezone: 'America/New_York',
718
+ })
719
+
720
+ assertEqual(result, true, 'Should block secondary template when shared limit reached')
721
+ }
722
+ })
723
+
724
+ // Test 22: otherTemplateIds - events of secondary template count toward primary booking
725
+ tests.push({
726
+ name: 'otherTemplateIds - secondary template events count when booking primary',
727
+ fn: () => {
728
+ const oct1 = new Date('2025-10-01T18:00:00.000Z').getTime()
729
+
730
+ const limits: CalendarEventLimit[] = [{
731
+ templateId: TEMPLATE_ID,
732
+ otherTemplateIds: [TEMPLATE_B],
733
+ period: 1,
734
+ limit: 1,
735
+ }]
736
+
737
+ const existingEvents = [
738
+ createEvent(oct1, TEMPLATE_B), // 1 event of secondary template
739
+ ]
740
+
741
+ // Booking primary template should be blocked because a secondary template event used the limit
742
+ const result = slot_violates_calendar_event_limits({
743
+ slotStartTimeInMS: oct1,
744
+ templateId: TEMPLATE_ID,
745
+ userId: USER_ID,
746
+ calendarEventLimits: limits,
747
+ existingEvents,
748
+ timezone: 'America/New_York',
749
+ })
750
+
751
+ assertEqual(result, true, 'Should block primary template when secondary events fill limit')
752
+ }
753
+ })
754
+
755
+ // Test 23: otherTemplateIds - mixed events from both templates count together
756
+ tests.push({
757
+ name: 'otherTemplateIds - mixed events count toward shared limit',
758
+ fn: () => {
759
+ const oct1_9am = new Date('2025-10-01T13:00:00.000Z').getTime()
760
+ const oct1_2pm = new Date('2025-10-01T18:00:00.000Z').getTime()
761
+ const oct1_5pm = new Date('2025-10-01T21:00:00.000Z').getTime()
762
+
763
+ const limits: CalendarEventLimit[] = [{
764
+ templateId: TEMPLATE_ID,
765
+ otherTemplateIds: [TEMPLATE_B],
766
+ period: 1,
767
+ limit: 2,
768
+ }]
769
+
770
+ const existingEvents = [
771
+ createEvent(oct1_9am, TEMPLATE_ID), // 1 primary
772
+ createEvent(oct1_2pm, TEMPLATE_B), // 1 secondary
773
+ ]
774
+
775
+ // Both count toward limit of 2, so a 3rd should be blocked
776
+ const result = slot_violates_calendar_event_limits({
777
+ slotStartTimeInMS: oct1_5pm,
778
+ templateId: TEMPLATE_ID,
779
+ userId: USER_ID,
780
+ calendarEventLimits: limits,
781
+ existingEvents,
782
+ timezone: 'America/New_York',
783
+ })
784
+
785
+ assertEqual(result, true, 'Should block when mixed events reach shared limit')
786
+ }
787
+ })
788
+
789
+ // Test 24: otherTemplateIds - unrelated template should NOT be affected
790
+ tests.push({
791
+ name: 'otherTemplateIds - unrelated template not affected by shared limit',
792
+ fn: () => {
793
+ const oct1 = new Date('2025-10-01T18:00:00.000Z').getTime()
794
+
795
+ const limits: CalendarEventLimit[] = [{
796
+ templateId: TEMPLATE_ID,
797
+ otherTemplateIds: [TEMPLATE_B],
798
+ period: 1,
799
+ limit: 1,
800
+ }]
801
+
802
+ const existingEvents = [
803
+ createEvent(oct1, TEMPLATE_ID), // Limit reached for A+B group
804
+ ]
805
+
806
+ // Template C is not part of the group, should be allowed
807
+ const result = slot_violates_calendar_event_limits({
808
+ slotStartTimeInMS: oct1,
809
+ templateId: TEMPLATE_C,
810
+ userId: USER_ID,
811
+ calendarEventLimits: limits,
812
+ existingEvents,
813
+ timezone: 'America/New_York',
814
+ })
815
+
816
+ assertEqual(result, false, 'Should allow unrelated template even when shared limit reached')
817
+ }
818
+ })
819
+
820
+ // Test 25: otherTemplateIds - under limit should allow
821
+ tests.push({
822
+ name: 'otherTemplateIds - under shared limit should allow',
823
+ fn: () => {
824
+ const oct1_9am = new Date('2025-10-01T13:00:00.000Z').getTime()
825
+ const oct1_2pm = new Date('2025-10-01T18:00:00.000Z').getTime()
826
+
827
+ const limits: CalendarEventLimit[] = [{
828
+ templateId: TEMPLATE_ID,
829
+ otherTemplateIds: [TEMPLATE_B],
830
+ period: 1,
831
+ limit: 3,
832
+ }]
833
+
834
+ const existingEvents = [
835
+ createEvent(oct1_9am, TEMPLATE_ID), // 1 event
836
+ createEvent(oct1_9am, TEMPLATE_B), // 1 event, total = 2
837
+ ]
838
+
839
+ // Under limit of 3, should allow
840
+ const result = slot_violates_calendar_event_limits({
841
+ slotStartTimeInMS: oct1_2pm,
842
+ templateId: TEMPLATE_B,
843
+ userId: USER_ID,
844
+ calendarEventLimits: limits,
845
+ existingEvents,
846
+ timezone: 'America/New_York',
847
+ })
848
+
849
+ assertEqual(result, false, 'Should allow when under shared limit')
850
+ }
851
+ })
852
+
853
+ // Test 26: No otherTemplateIds - backward compatibility
854
+ tests.push({
855
+ name: 'No otherTemplateIds - backward compatible single-template behavior',
856
+ fn: () => {
857
+ const oct1 = new Date('2025-10-01T18:00:00.000Z').getTime()
858
+
859
+ const limits: CalendarEventLimit[] = [{
860
+ templateId: TEMPLATE_ID,
861
+ period: 1,
862
+ limit: 1,
863
+ }]
864
+
865
+ const existingEvents = [
866
+ createEvent(oct1, TEMPLATE_B), // Event of different template
867
+ ]
868
+
869
+ // Without otherTemplateIds, template B events should NOT count toward template A's limit
870
+ const result = slot_violates_calendar_event_limits({
871
+ slotStartTimeInMS: oct1,
872
+ templateId: TEMPLATE_ID,
873
+ userId: USER_ID,
874
+ calendarEventLimits: limits,
875
+ existingEvents,
876
+ timezone: 'America/New_York',
877
+ })
878
+
879
+ assertEqual(result, false, 'Should not count other template events without otherTemplateIds')
880
+ }
881
+ })
882
+
688
883
  return tests
689
884
  }
690
885