@tellescope/sdk 1.256.6 → 1.256.8

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 (128) hide show
  1. package/lib/cjs/enduser.d.ts +1 -1
  2. package/lib/cjs/enduser.d.ts.map +1 -1
  3. package/lib/cjs/enduser.js +1 -0
  4. package/lib/cjs/enduser.js.map +1 -1
  5. package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
  6. package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +505 -0
  8. package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/configurations_public_read.test.js +173 -0
  12. package/lib/cjs/tests/api_tests/configurations_public_read.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
  14. package/lib/cjs/tests/api_tests/form_started_trigger.test.js +87 -24
  15. package/lib/cjs/tests/api_tests/form_started_trigger.test.js.map +1 -1
  16. package/lib/cjs/tests/api_tests/form_translations.test.d.ts +6 -0
  17. package/lib/cjs/tests/api_tests/form_translations.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/form_translations.test.js +259 -0
  19. package/lib/cjs/tests/api_tests/form_translations.test.js.map +1 -0
  20. package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
  21. package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
  22. package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js +293 -0
  23. package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
  24. package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
  25. package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
  26. package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
  27. package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
  28. package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
  29. package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js +277 -0
  30. package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
  31. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
  32. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +262 -78
  33. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
  34. package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
  35. package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
  36. package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +315 -0
  37. package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
  38. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
  39. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
  40. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
  41. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
  42. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js +343 -80
  43. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
  44. package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
  45. package/lib/cjs/tests/api_tests/welle_partner_token.test.js +74 -35
  46. package/lib/cjs/tests/api_tests/welle_partner_token.test.js.map +1 -1
  47. package/lib/cjs/tests/stripe_webhook_helpers.d.ts +134 -0
  48. package/lib/cjs/tests/stripe_webhook_helpers.d.ts.map +1 -1
  49. package/lib/cjs/tests/stripe_webhook_helpers.js +126 -1
  50. package/lib/cjs/tests/stripe_webhook_helpers.js.map +1 -1
  51. package/lib/cjs/tests/tests.d.ts.map +1 -1
  52. package/lib/cjs/tests/tests.js +242 -212
  53. package/lib/cjs/tests/tests.js.map +1 -1
  54. package/lib/esm/enduser.d.ts +2 -1
  55. package/lib/esm/enduser.d.ts.map +1 -1
  56. package/lib/esm/enduser.js +1 -0
  57. package/lib/esm/enduser.js.map +1 -1
  58. package/lib/esm/sdk.d.ts +3 -2
  59. package/lib/esm/sdk.d.ts.map +1 -1
  60. package/lib/esm/session.d.ts +1 -0
  61. package/lib/esm/session.d.ts.map +1 -1
  62. package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
  63. package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
  64. package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +501 -0
  65. package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
  66. package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts +6 -0
  67. package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
  68. package/lib/esm/tests/api_tests/configurations_public_read.test.js +169 -0
  69. package/lib/esm/tests/api_tests/configurations_public_read.test.js.map +1 -0
  70. package/lib/esm/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
  71. package/lib/esm/tests/api_tests/form_started_trigger.test.js +87 -24
  72. package/lib/esm/tests/api_tests/form_started_trigger.test.js.map +1 -1
  73. package/lib/esm/tests/api_tests/form_translations.test.d.ts +6 -0
  74. package/lib/esm/tests/api_tests/form_translations.test.d.ts.map +1 -0
  75. package/lib/esm/tests/api_tests/form_translations.test.js +255 -0
  76. package/lib/esm/tests/api_tests/form_translations.test.js.map +1 -0
  77. package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
  78. package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
  79. package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js +289 -0
  80. package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
  81. package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
  82. package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
  83. package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
  84. package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
  85. package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
  86. package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js +273 -0
  87. package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
  88. package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
  89. package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +262 -78
  90. package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
  91. package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
  92. package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
  93. package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +311 -0
  94. package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
  95. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
  96. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
  97. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
  98. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
  99. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js +344 -81
  100. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
  101. package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
  102. package/lib/esm/tests/api_tests/welle_partner_token.test.js +74 -35
  103. package/lib/esm/tests/api_tests/welle_partner_token.test.js.map +1 -1
  104. package/lib/esm/tests/stripe_webhook_helpers.d.ts +134 -0
  105. package/lib/esm/tests/stripe_webhook_helpers.d.ts.map +1 -1
  106. package/lib/esm/tests/stripe_webhook_helpers.js +119 -0
  107. package/lib/esm/tests/stripe_webhook_helpers.js.map +1 -1
  108. package/lib/esm/tests/tests.d.ts.map +1 -1
  109. package/lib/esm/tests/tests.js +242 -212
  110. package/lib/esm/tests/tests.js.map +1 -1
  111. package/lib/tsconfig.tsbuildinfo +1 -1
  112. package/package.json +10 -10
  113. package/src/enduser.ts +4 -2
  114. package/src/tests/api_tests/calendar_event_create_and_bulk_triggers.test.ts +293 -0
  115. package/src/tests/api_tests/configurations_public_read.test.ts +116 -0
  116. package/src/tests/api_tests/form_started_trigger.test.ts +49 -1
  117. package/src/tests/api_tests/form_translations.test.ts +205 -0
  118. package/src/tests/api_tests/group_event_attendee_self_update.test.ts +161 -0
  119. package/src/tests/api_tests/group_event_attendee_status_triggers.test.ts +135 -0
  120. package/src/tests/api_tests/mdi_trigger_refill.test.ts +184 -0
  121. package/src/tests/api_tests/purchase_made_trigger.test.ts +137 -0
  122. package/src/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.ts +204 -0
  123. package/src/tests/api_tests/security/public_endpoint_auth_field_lock.test.ts +16 -6
  124. package/src/tests/api_tests/stripe_webhook_triggers.test.ts +221 -0
  125. package/src/tests/api_tests/welle_partner_token.test.ts +49 -6
  126. package/src/tests/stripe_webhook_helpers.ts +116 -0
  127. package/src/tests/tests.ts +29 -12
  128. package/test_generated.pdf +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/sdk",
3
- "version": "1.256.6",
3
+ "version": "1.256.8",
4
4
  "description": "Code for interacting with the Tellescope API",
5
5
  "main": "./lib/cjs/sdk.js",
6
6
  "module": "./lib/esm/sdk.js",
@@ -32,14 +32,14 @@
32
32
  },
33
33
  "homepage": "https://github.com/tellescope-os/tellescope#readme",
34
34
  "dependencies": {
35
- "@tellescope/constants": "1.256.6",
36
- "@tellescope/schema": "1.256.6",
37
- "@tellescope/testing": "1.256.6",
38
- "@tellescope/types-client": "1.256.6",
39
- "@tellescope/types-models": "1.256.6",
40
- "@tellescope/types-utilities": "1.256.6",
41
- "@tellescope/utilities": "1.256.6",
42
- "@tellescope/validation": "1.256.6",
35
+ "@tellescope/constants": "1.256.7",
36
+ "@tellescope/schema": "1.256.8",
37
+ "@tellescope/testing": "1.256.7",
38
+ "@tellescope/types-client": "1.256.7",
39
+ "@tellescope/types-models": "1.256.7",
40
+ "@tellescope/types-utilities": "1.256.7",
41
+ "@tellescope/utilities": "1.256.7",
42
+ "@tellescope/validation": "1.256.7",
43
43
  "axios": "0.30.3",
44
44
  "dotenv": "14.3.2",
45
45
  "form-data": "4.0.4",
@@ -55,5 +55,5 @@
55
55
  "publishConfig": {
56
56
  "access": "public"
57
57
  },
58
- "gitHead": "b5672761d4ea6995488faaeb9a0ccc12436f7b30"
58
+ "gitHead": "194f5c52adb83f8950ad515f37a9c638b8233c90"
59
59
  }
package/src/enduser.ts CHANGED
@@ -59,6 +59,7 @@ type EnduserAccessibleModels = (
59
59
  | 'allergy_codes'
60
60
  | 'forms'
61
61
  | 'enduser_eligibility_results'
62
+ | 'configurations' // reads reduced to publicRead: true records (form translation maps)
62
63
  )
63
64
 
64
65
  export const defaultQueries = <N extends keyof ClientModelForName>(
@@ -281,8 +282,9 @@ const loadDefaultQueries = (s: EnduserSession): { [K in EnduserAccessibleModels]
281
282
  enduser_problems: defaultQueries(s, 'enduser_problems'),
282
283
  diagnosis_codes: defaultQueries(s, 'diagnosis_codes'),
283
284
  allergy_codes: defaultQueries(s, 'allergy_codes'),
284
- forms: defaultQueries(s, 'forms'),
285
- enduser_eligibility_results: defaultQueries(s, 'enduser_eligibility_results'),
285
+ forms: defaultQueries(s, 'forms'),
286
+ enduser_eligibility_results: defaultQueries(s, 'enduser_eligibility_results'),
287
+ configurations: defaultQueries(s, 'configurations'), // reads reduced to publicRead: true records (form translation maps)
286
288
  })
287
289
 
288
290
 
@@ -0,0 +1,293 @@
1
+ import { Session } from "../../sdk"
2
+ import { log_header, wait, async_test } from "@tellescope/testing"
3
+ import { setup_tests } from "../setup"
4
+
5
+ const host = process.env.API_URL || "http://localhost:8080"
6
+
7
+ // Every trigger in this suite is filtered to this single title, so the suite can neither perturb nor
8
+ // be perturbed by other suites' calendar events. Assertions use includes()/!includes() rather than
9
+ // exact tag counts for the same reason.
10
+ const TITLE = 'Create Bulk Trigger Test'
11
+
12
+ const TAG = {
13
+ Created: 'CB: Created',
14
+ Completed: 'CB: Completed',
15
+ 'No-showed': 'CB: No-showed',
16
+ Cancelled: 'CB: Cancelled',
17
+ CancelledByUser: 'CB: Cancelled by user',
18
+ } as const
19
+
20
+ export const calendar_event_create_and_bulk_triggers_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
21
+ log_header("Automation Trigger Tests (Create Path + bulk_update)")
22
+
23
+ const createdTriggerIds: string[] = []
24
+ const createdEnduserIds: string[] = []
25
+ const createdEventIds: string[] = []
26
+
27
+ const createEnduser = async () => {
28
+ const e = await sdk.api.endusers.createOne({})
29
+ createdEnduserIds.push(e.id)
30
+ return e
31
+ }
32
+ const createEvent = async (overrides: Record<string, any> = {}) => {
33
+ const event = await sdk.api.calendar_events.createOne({
34
+ title: TITLE,
35
+ durationInMinutes: 30,
36
+ startTimeInMS: Date.now() + Math.floor(Math.random() * 100000),
37
+ ...overrides,
38
+ } as any)
39
+ createdEventIds.push(event.id)
40
+ return event
41
+ }
42
+
43
+ const tagsOf = async (enduserId: string) => (await sdk.api.endusers.getOne(enduserId)).tags || []
44
+
45
+ try {
46
+ for (const [type, info, tag] of [
47
+ ['Appointment Created', { titles: [TITLE] }, TAG.Created],
48
+ ['Appointment Completed', { titles: [TITLE] }, TAG.Completed],
49
+ ['Appointment No-Showed', { titles: [TITLE] }, TAG['No-showed']],
50
+ ['Appointment Cancelled', { titles: [TITLE] }, TAG.Cancelled],
51
+ ['Appointment Cancelled', { titles: [TITLE], by: 'user' }, TAG.CancelledByUser],
52
+ ] as const) {
53
+ const t = await sdk.api.automation_triggers.createOne({
54
+ event: { type, info } as any,
55
+ action: { type: 'Add Tags', info: { tags: [tag] } },
56
+ status: 'Active',
57
+ title: tag,
58
+ })
59
+ createdTriggerIds.push(t.id)
60
+ }
61
+
62
+ // ============================================================
63
+ // SECTION 1: single create path, with per-attendee statuses already set at creation.
64
+ // Covers the completed-at-create branch of handle_new_events_trigger, which routes through
65
+ // enduser_ids_for_event_wide_completion. Nothing else in the test suite creates an event with
66
+ // attendeeStatuses / cancelledGroupAttendees preset.
67
+ // ============================================================
68
+ log_header("Create-time per-attendee statuses respected by completed-at-create")
69
+
70
+ const createNoShow = await createEnduser()
71
+ const createLegacyCancelled = await createEnduser()
72
+ const createActive = await createEnduser()
73
+
74
+ await createEvent({
75
+ attendees: [
76
+ { type: 'enduser', id: createNoShow.id },
77
+ { type: 'enduser', id: createLegacyCancelled.id },
78
+ { type: 'enduser', id: createActive.id },
79
+ ],
80
+ completedAt: new Date(),
81
+ completedBy: sdk.userInfo.id,
82
+ attendeeStatuses: [{ id: createNoShow.id, at: new Date(), status: 'No-showed' }],
83
+ // legacy shape: cancelled with no attendeeStatuses entry (what the portal writes)
84
+ cancelledGroupAttendees: [{ id: createLegacyCancelled.id, at: new Date() }],
85
+ })
86
+ await wait(undefined, 1000)
87
+
88
+ await async_test(
89
+ "completed-at-create fires Completed for the un-statused attendee",
90
+ () => tagsOf(createActive.id),
91
+ { onResult: (tags: string[]) => tags.includes(TAG.Completed) }
92
+ )
93
+ await async_test(
94
+ "completed-at-create does NOT fire Completed for a 'No-showed' attendee",
95
+ () => tagsOf(createNoShow.id),
96
+ { onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
97
+ )
98
+ await async_test(
99
+ "completed-at-create does NOT fire Completed for a cancelledGroupAttendees attendee",
100
+ () => tagsOf(createLegacyCancelled.id),
101
+ { onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
102
+ )
103
+ // the create path has no per-attendee status dispatch (that only runs as an update side effect),
104
+ // so a status set at creation fires nothing of its own — pinning current behavior
105
+ await async_test(
106
+ "a 'No-showed' status set at creation fires no No-Showed trigger",
107
+ () => tagsOf(createNoShow.id),
108
+ { onResult: (tags: string[]) => !tags.includes(TAG['No-showed']) }
109
+ )
110
+ // an event completed at creation uses the Completed trigger only, never Created
111
+ for (const [label, e] of [
112
+ ['no-showed', createNoShow], ['legacy-cancelled', createLegacyCancelled], ['active', createActive],
113
+ ] as const) {
114
+ await async_test(
115
+ `completed-at-create does NOT fire Created for the ${label} attendee`,
116
+ () => tagsOf(e.id),
117
+ { onResult: (tags: string[]) => !tags.includes(TAG.Created) }
118
+ )
119
+ }
120
+
121
+ // ============================================================
122
+ // SECTION 2: bulk create path (createSome with >1 event) — handle_new_events_trigger returns early
123
+ // for a single event, so more than one event is required to route through bulk_handle_actions_for_triggers.
124
+ // ============================================================
125
+ log_header("Bulk create path respects per-attendee statuses")
126
+
127
+ const bulkNoShow = await createEnduser()
128
+ const bulkActive = await createEnduser()
129
+ const bulkCreated = await createEnduser()
130
+
131
+ const bulkResult = await sdk.api.calendar_events.createSome([
132
+ {
133
+ title: TITLE,
134
+ durationInMinutes: 30,
135
+ startTimeInMS: Date.now(),
136
+ attendees: [{ type: 'enduser', id: bulkNoShow.id }, { type: 'enduser', id: bulkActive.id }],
137
+ completedAt: new Date(),
138
+ completedBy: sdk.userInfo.id,
139
+ attendeeStatuses: [{ id: bulkNoShow.id, at: new Date(), status: 'No-showed' }],
140
+ },
141
+ {
142
+ title: TITLE,
143
+ durationInMinutes: 30,
144
+ startTimeInMS: Date.now(),
145
+ attendees: [{ type: 'enduser', id: bulkCreated.id }],
146
+ },
147
+ ] as any)
148
+ createdEventIds.push(...bulkResult.created.map(e => e.id))
149
+ await wait(undefined, 1000)
150
+
151
+ // routing canary: a single created event short-circuits before the bulk handler, so this suite only
152
+ // covers the bulk branch as long as both events are created in one request
153
+ await async_test(
154
+ "bulk create routing canary: both events created in one request",
155
+ async () => bulkResult.created.length,
156
+ { onResult: (count: number) => count === 2 }
157
+ )
158
+ await async_test(
159
+ "bulk create fires Completed for the un-statused attendee",
160
+ () => tagsOf(bulkActive.id),
161
+ { onResult: (tags: string[]) => tags.includes(TAG.Completed) }
162
+ )
163
+ await async_test(
164
+ "bulk create does NOT fire Completed for a 'No-showed' attendee",
165
+ () => tagsOf(bulkNoShow.id),
166
+ { onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
167
+ )
168
+ await async_test(
169
+ "bulk create fires Created for an attendee of the non-completed event",
170
+ () => tagsOf(bulkCreated.id),
171
+ { onResult: (tags: string[]) => tags.includes(TAG.Created) }
172
+ )
173
+ await async_test(
174
+ "bulk create does NOT fire Created for attendees of the completed event",
175
+ () => tagsOf(bulkActive.id),
176
+ { onResult: (tags: string[]) => !tags.includes(TAG.Created) }
177
+ )
178
+
179
+ // ============================================================
180
+ // SECTION 3: Appointment Created fans out to every attendee of a group event.
181
+ // ============================================================
182
+ log_header("Appointment Created fires for all attendees of a group event")
183
+
184
+ const created1 = await createEnduser()
185
+ const created2 = await createEnduser()
186
+ const created3 = await createEnduser()
187
+
188
+ await createEvent({
189
+ attendees: [
190
+ { type: 'enduser', id: created1.id },
191
+ { type: 'enduser', id: created2.id },
192
+ { type: 'enduser', id: created3.id },
193
+ ],
194
+ })
195
+ await wait(undefined, 1000)
196
+
197
+ const groupCreatedAttendees = [created1, created2, created3]
198
+ for (let i = 0; i < groupCreatedAttendees.length; i++) {
199
+ const enduserId = groupCreatedAttendees[i].id
200
+ await async_test(
201
+ `group event fires Created for attendee #${i + 1}`,
202
+ () => tagsOf(enduserId),
203
+ { onResult: (tags: string[]) => tags.includes(TAG.Created) }
204
+ )
205
+ }
206
+
207
+ // ============================================================
208
+ // SECTION 4: bulk_update action 'no_show' fires the No-Showed trigger.
209
+ // ============================================================
210
+ log_header("bulk_update 'no_show' fires No-Showed for all attendees")
211
+
212
+ const noShowA = await createEnduser()
213
+ const noShowB = await createEnduser()
214
+ const noShowEvent = await createEvent({
215
+ attendees: [{ type: 'enduser', id: noShowA.id }, { type: 'enduser', id: noShowB.id }],
216
+ })
217
+
218
+ await sdk.api.calendar_events.bulk_update({ ids: [noShowEvent.id], action: 'no_show' })
219
+ await wait(undefined, 1000)
220
+
221
+ await async_test(
222
+ "bulk_update 'no_show' fires No-Showed for attendee #1",
223
+ () => tagsOf(noShowA.id),
224
+ { onResult: (tags: string[]) => tags.includes(TAG['No-showed']) }
225
+ )
226
+ await async_test(
227
+ "bulk_update 'no_show' fires No-Showed for attendee #2",
228
+ () => tagsOf(noShowB.id),
229
+ { onResult: (tags: string[]) => tags.includes(TAG['No-showed']) }
230
+ )
231
+
232
+ // ============================================================
233
+ // SECTION 5: bulk_update action 'cancel' fires the Cancelled trigger, including by:'user'
234
+ // (the bulk cancel writes statusChangeSource: { source: 'user' }).
235
+ // ============================================================
236
+ log_header("bulk_update 'cancel' fires Cancelled for all attendees")
237
+
238
+ const cancelA = await createEnduser()
239
+ const cancelB = await createEnduser()
240
+ const cancelEvent = await createEvent({
241
+ attendees: [{ type: 'enduser', id: cancelA.id }, { type: 'enduser', id: cancelB.id }],
242
+ })
243
+
244
+ await sdk.api.calendar_events.bulk_update({ ids: [cancelEvent.id], action: 'cancel' })
245
+ await wait(undefined, 1000)
246
+
247
+ for (const [label, e] of [['#1', cancelA], ['#2', cancelB]] as const) {
248
+ await async_test(
249
+ `bulk_update 'cancel' fires Cancelled for attendee ${label}`,
250
+ () => tagsOf(e.id),
251
+ { onResult: (tags: string[]) => tags.includes(TAG.Cancelled) }
252
+ )
253
+ await async_test(
254
+ `bulk_update 'cancel' fires by:'user' Cancelled for attendee ${label}`,
255
+ () => tagsOf(e.id),
256
+ { onResult: (tags: string[]) => tags.includes(TAG.CancelledByUser) }
257
+ )
258
+ }
259
+
260
+ } finally {
261
+ for (const id of createdEventIds) {
262
+ try { await sdk.api.calendar_events.deleteOne(id) } catch (_) {}
263
+ }
264
+ for (const id of createdEnduserIds) {
265
+ try { await sdk.api.endusers.deleteOne(id) } catch (_) {}
266
+ }
267
+ for (const id of createdTriggerIds) {
268
+ try { await sdk.api.automation_triggers.deleteOne(id) } catch (_) {}
269
+ }
270
+ }
271
+ }
272
+
273
+ // Allow running this test file independently
274
+ if (require.main === module) {
275
+ console.log(`🌐 Using API URL: ${host}`)
276
+ const sdk = new Session({ host })
277
+ const sdkNonAdmin = new Session({ host })
278
+
279
+ const runTests = async () => {
280
+ await setup_tests(sdk, sdkNonAdmin)
281
+ await calendar_event_create_and_bulk_triggers_tests({ sdk, sdkNonAdmin })
282
+ }
283
+
284
+ runTests()
285
+ .then(() => {
286
+ console.log("✅ Calendar event create path + bulk_update trigger tests completed successfully")
287
+ process.exit(0)
288
+ })
289
+ .catch((error) => {
290
+ console.error("❌ Calendar event create path + bulk_update trigger tests failed:", error)
291
+ process.exit(1)
292
+ })
293
+ }
@@ -0,0 +1,116 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session, EnduserSession } from "../../sdk"
4
+ import {
5
+ assert,
6
+ async_test,
7
+ handleAnyError,
8
+ log_header,
9
+ } from "@tellescope/testing"
10
+ import { setup_tests } from "../setup"
11
+
12
+ const host = process.env.API_URL || 'http://localhost:8080' as const
13
+
14
+ // Modular test for the configurations publicRead flag (added for AI form translation maps).
15
+ //
16
+ // The critical security property: configurations hold sensitive integration settings (API keys etc.),
17
+ // so enduser read access is opt-in PER RECORD via publicRead: true. A configuration without the flag
18
+ // must remain completely invisible to enduser sessions, and endusers must never be able to write
19
+ // configurations at all.
20
+ export const configurations_public_read_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
21
+ log_header("Configurations publicRead Tests")
22
+
23
+ const createdConfigurationIds: string[] = []
24
+ let enduserId: string | undefined
25
+
26
+ try {
27
+ // config A simulates an integration config (no flag); config B is a flagged translation-map-style record
28
+ const configA = await sdk.api.configurations.createOne({
29
+ type: 'test-private-configuration',
30
+ value: JSON.stringify({ apiKey: 'super-secret' }),
31
+ })
32
+ createdConfigurationIds.push(configA.id)
33
+
34
+ const configB = await sdk.api.configurations.createOne({
35
+ type: 'test-public-configuration',
36
+ value: JSON.stringify({ Hello: 'Hola' }),
37
+ publicRead: true,
38
+ })
39
+ createdConfigurationIds.push(configB.id)
40
+
41
+ const enduser = await sdk.api.endusers.createOne({
42
+ email: `configurations_public_read_${Date.now()}@test.tellescope.com`,
43
+ })
44
+ enduserId = enduser.id
45
+
46
+ const { authToken, enduser: authEnduser } = await sdk.api.endusers.generate_auth_token({ id: enduser.id })
47
+ const enduserSDK = new EnduserSession({ host, authToken, enduser: authEnduser, businessId: sdk.userInfo.businessId })
48
+
49
+ // --- reads ---
50
+ await async_test(
51
+ 'enduser CAN read a publicRead configuration by id',
52
+ () => enduserSDK.api.configurations.getOne(configB.id),
53
+ { onResult: c => c?.id === configB.id && c.value === configB.value },
54
+ )
55
+ await async_test(
56
+ 'enduser CANNOT read an unflagged configuration by id (key security assertion)',
57
+ () => enduserSDK.api.configurations.getOne(configA.id),
58
+ handleAnyError,
59
+ )
60
+ await async_test(
61
+ 'enduser getSome returns only publicRead configurations',
62
+ () => enduserSDK.api.configurations.getSome({}),
63
+ { onResult: configurations => (
64
+ configurations.some(c => c.id === configB.id)
65
+ && !configurations.some(c => c.id === configA.id)
66
+ )},
67
+ )
68
+
69
+ // --- writes: endusers have no create/update/delete on configurations ---
70
+ await async_test(
71
+ 'enduser CANNOT create a configuration',
72
+ () => enduserSDK.api.configurations.createOne({ type: 'test-enduser-create', value: '{}', publicRead: true }),
73
+ handleAnyError,
74
+ )
75
+ await async_test(
76
+ 'enduser CANNOT update a configuration (even a publicRead one)',
77
+ () => enduserSDK.api.configurations.updateOne(configB.id, { value: '{}' }),
78
+ handleAnyError,
79
+ )
80
+ await async_test(
81
+ 'enduser CANNOT delete a configuration',
82
+ () => enduserSDK.api.configurations.deleteOne(configB.id),
83
+ handleAnyError,
84
+ )
85
+
86
+ // staff access is unchanged by the flag
87
+ const staffRead = await sdk.api.configurations.getOne(configA.id)
88
+ assert(staffRead?.id === configA.id, 'staff could not read unflagged configuration', 'staff reads unflagged configuration')
89
+ } finally {
90
+ for (const id of createdConfigurationIds) {
91
+ await sdk.api.configurations.deleteOne(id).catch(() => {})
92
+ }
93
+ if (enduserId) await sdk.api.endusers.deleteOne(enduserId).catch(() => {})
94
+ }
95
+ }
96
+
97
+ // Allow running this test file independently
98
+ if (require.main === module) {
99
+ const sdk = new Session({ host })
100
+ const sdkNonAdmin = new Session({ host })
101
+
102
+ const runTests = async () => {
103
+ await setup_tests(sdk, sdkNonAdmin)
104
+ await configurations_public_read_tests({ sdk, sdkNonAdmin })
105
+ }
106
+
107
+ runTests()
108
+ .then(() => {
109
+ console.log("✅ Configurations publicRead test suite completed successfully")
110
+ process.exit(0)
111
+ })
112
+ .catch((error) => {
113
+ console.error("❌ Configurations publicRead test suite failed:", error)
114
+ process.exit(1)
115
+ })
116
+ }
@@ -30,13 +30,14 @@ export const form_started_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk: S
30
30
  }
31
31
 
32
32
  // Helper to start a public form session (this triggers Form Started)
33
- const startPublicForm = async (email: string) => {
33
+ const startPublicForm = async (email: string, publicIdentifier?: string) => {
34
34
  const enduserSDK = new EnduserSession({ host, businessId: form.businessId })
35
35
  return await enduserSDK.api.form_responses.session_for_public_form({
36
36
  formId: form.id,
37
37
  businessId: form.businessId,
38
38
  email,
39
39
  skipMatch: true,
40
+ ...publicIdentifier ? { publicIdentifier } : {},
40
41
  })
41
42
  }
42
43
 
@@ -257,11 +258,58 @@ export const form_started_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk: S
257
258
 
258
259
  await sdk.api.automation_triggers.deleteOne(triggerFinalized.id)
259
260
 
261
+ // Test 7: {{PUBLIC_IDENTIFIER}} resolves in Add Tags / Set Fields, matching Form Submitted
262
+ const triggerPublicIdentifierTag = await sdk.api.automation_triggers.createOne({
263
+ event: { type: 'Form Started', info: { formIds: [form.id] } },
264
+ action: { type: 'Add Tags', info: { tags: ['{{PUBLIC_IDENTIFIER}}'] } },
265
+ status: 'Active',
266
+ title: "Public identifier tag",
267
+ })
268
+ const triggerPublicIdentifierField = await sdk.api.automation_triggers.createOne({
269
+ event: { type: 'Form Started', info: { formIds: [form.id] } },
270
+ action: { type: 'Set Fields', info: { fields: [{
271
+ name: 'fstSource', type: 'Custom Value', value: '{{PUBLIC_IDENTIFIER}}',
272
+ }] } },
273
+ status: 'Active',
274
+ title: "Public identifier field",
275
+ })
276
+
277
+ const PUBLIC_IDENTIFIER = 'fst-pubid-abc123'
278
+ const { enduserId: enduserId11 } = await startPublicForm('fst-test11@tellescope.com', PUBLIC_IDENTIFIER)
279
+ await wait(undefined, 500)
280
+ await async_test(
281
+ "{{PUBLIC_IDENTIFIER}} resolves to the identifier in Add Tags",
282
+ () => sdk.api.endusers.getOne(enduserId11),
283
+ { onResult: (e: Enduser) => (
284
+ !!e.tags?.includes(PUBLIC_IDENTIFIER) && !e.tags?.includes('{{PUBLIC_IDENTIFIER}}')
285
+ )}
286
+ )
287
+ await async_test(
288
+ "{{PUBLIC_IDENTIFIER}} resolves to the identifier in Set Fields",
289
+ () => sdk.api.endusers.getOne(enduserId11),
290
+ { onResult: (e: Enduser) => e.fields?.fstSource === PUBLIC_IDENTIFIER }
291
+ )
292
+
293
+ // no publicIdentifier on the link => literal is left as-is (same guard as Form Submitted)
294
+ const { enduserId: enduserId12 } = await startPublicForm('fst-test12@tellescope.com')
295
+ await wait(undefined, 500)
296
+ await async_test(
297
+ "{{PUBLIC_IDENTIFIER}} left as literal when form started without an identifier",
298
+ () => sdk.api.endusers.getOne(enduserId12),
299
+ { onResult: (e: Enduser) => (
300
+ !!e.tags?.includes('{{PUBLIC_IDENTIFIER}}') && e.fields?.fstSource === '{{PUBLIC_IDENTIFIER}}'
301
+ )}
302
+ )
303
+
304
+ await sdk.api.automation_triggers.deleteOne(triggerPublicIdentifierTag.id)
305
+ await sdk.api.automation_triggers.deleteOne(triggerPublicIdentifierField.id)
306
+
260
307
  // Cleanup - delete all endusers created during test
261
308
  const enduserEmails = [
262
309
  'fst-test1@tellescope.com', 'fst-test2@tellescope.com', 'fst-test3@tellescope.com',
263
310
  'fst-test4@tellescope.com', 'fst-test5@tellescope.com', 'fst-test7@tellescope.com',
264
311
  'fst-test8@tellescope.com', 'fst-test9@tellescope.com', 'fst-test10@tellescope.com',
312
+ 'fst-test11@tellescope.com', 'fst-test12@tellescope.com',
265
313
  'fst-test-prepare1@tellescope.com', 'fst-test-prepare2@tellescope.com',
266
314
  ]
267
315
  for (const email of enduserEmails) {