@tellescope/sdk 1.250.1 → 1.251.0

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 (96) hide show
  1. package/lib/cjs/sdk.d.ts +9 -0
  2. package/lib/cjs/sdk.d.ts.map +1 -1
  3. package/lib/cjs/sdk.js +3 -0
  4. package/lib/cjs/sdk.js.map +1 -1
  5. package/lib/cjs/tests/api_tests/account_switcher.test.d.ts.map +1 -1
  6. package/lib/cjs/tests/api_tests/account_switcher.test.js +1700 -306
  7. package/lib/cjs/tests/api_tests/account_switcher.test.js.map +1 -1
  8. package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.d.ts.map +1 -1
  9. package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js +28 -15
  10. package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
  11. package/lib/cjs/tests/api_tests/enduser_login.test.d.ts +6 -0
  12. package/lib/cjs/tests/api_tests/enduser_login.test.d.ts.map +1 -0
  13. package/lib/cjs/tests/api_tests/enduser_login.test.js +315 -0
  14. package/lib/cjs/tests/api_tests/enduser_login.test.js.map +1 -0
  15. package/lib/cjs/tests/api_tests/medication_added_trigger.test.d.ts.map +1 -1
  16. package/lib/cjs/tests/api_tests/medication_added_trigger.test.js +556 -105
  17. package/lib/cjs/tests/api_tests/medication_added_trigger.test.js.map +1 -1
  18. package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.d.ts +7 -0
  19. package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.d.ts.map +1 -0
  20. package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.js +436 -0
  21. package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.js.map +1 -0
  22. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
  23. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
  24. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js +370 -0
  25. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
  26. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
  27. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
  28. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +373 -0
  29. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
  30. package/lib/cjs/tests/setup.d.ts.map +1 -1
  31. package/lib/cjs/tests/setup.js +47 -32
  32. package/lib/cjs/tests/setup.js.map +1 -1
  33. package/lib/cjs/tests/tests.d.ts.map +1 -1
  34. package/lib/cjs/tests/tests.js +190 -161
  35. package/lib/cjs/tests/tests.js.map +1 -1
  36. package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.d.ts +3 -0
  37. package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.d.ts.map +1 -0
  38. package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.js +114 -0
  39. package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.js.map +1 -0
  40. package/lib/esm/sdk.d.ts +9 -0
  41. package/lib/esm/sdk.d.ts.map +1 -1
  42. package/lib/esm/sdk.js +3 -0
  43. package/lib/esm/sdk.js.map +1 -1
  44. package/lib/esm/tests/api_tests/account_switcher.test.d.ts.map +1 -1
  45. package/lib/esm/tests/api_tests/account_switcher.test.js +1702 -305
  46. package/lib/esm/tests/api_tests/account_switcher.test.js.map +1 -1
  47. package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.d.ts.map +1 -1
  48. package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js +28 -15
  49. package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
  50. package/lib/esm/tests/api_tests/enduser_login.test.d.ts +6 -0
  51. package/lib/esm/tests/api_tests/enduser_login.test.d.ts.map +1 -0
  52. package/lib/esm/tests/api_tests/enduser_login.test.js +308 -0
  53. package/lib/esm/tests/api_tests/enduser_login.test.js.map +1 -0
  54. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts +6 -0
  55. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts.map +1 -0
  56. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js +268 -0
  57. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js.map +1 -0
  58. package/lib/esm/tests/api_tests/medication_added_trigger.test.d.ts.map +1 -1
  59. package/lib/esm/tests/api_tests/medication_added_trigger.test.js +556 -105
  60. package/lib/esm/tests/api_tests/medication_added_trigger.test.js.map +1 -1
  61. package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.d.ts +7 -0
  62. package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.d.ts.map +1 -0
  63. package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.js +432 -0
  64. package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.js.map +1 -0
  65. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
  66. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
  67. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js +366 -0
  68. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
  69. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
  70. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
  71. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +369 -0
  72. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
  73. package/lib/esm/tests/setup.d.ts.map +1 -1
  74. package/lib/esm/tests/setup.js +47 -32
  75. package/lib/esm/tests/setup.js.map +1 -1
  76. package/lib/esm/tests/tests.d.ts.map +1 -1
  77. package/lib/esm/tests/tests.js +190 -161
  78. package/lib/esm/tests/tests.js.map +1 -1
  79. package/lib/esm/tests/unit_tests/conditional_logic_medication.test.d.ts +3 -0
  80. package/lib/esm/tests/unit_tests/conditional_logic_medication.test.d.ts.map +1 -0
  81. package/lib/esm/tests/unit_tests/conditional_logic_medication.test.js +111 -0
  82. package/lib/esm/tests/unit_tests/conditional_logic_medication.test.js.map +1 -0
  83. package/lib/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +10 -10
  85. package/src/sdk.ts +12 -0
  86. package/src/tests/api_tests/account_switcher.test.ts +1283 -0
  87. package/src/tests/api_tests/enduser_cross_access_isolation.test.ts +26 -0
  88. package/src/tests/api_tests/enduser_login.test.ts +215 -0
  89. package/src/tests/api_tests/medication_added_trigger.test.ts +345 -4
  90. package/src/tests/api_tests/outbound_chat_sent_trigger.test.ts +339 -0
  91. package/src/tests/api_tests/push_forms_to_portal_group_completion.test.ts +198 -0
  92. package/src/tests/api_tests/set_fields_order_templates.test.ts +258 -0
  93. package/src/tests/setup.ts +8 -1
  94. package/src/tests/tests.ts +23 -6
  95. package/src/tests/unit_tests/conditional_logic_medication.test.ts +133 -0
  96. package/test_generated.pdf +0 -0
@@ -0,0 +1,258 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ assert,
6
+ async_test,
7
+ log_header,
8
+ wait,
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
+ const TRIGGER_TITLE_BLOCK_A = "Order Templates: Block A"
15
+ const TRIGGER_TITLE_BLOCK_B = "Order Templates: Block B"
16
+
17
+ const buildSetFieldsAction = (prefix: string) => ({
18
+ type: 'Set Fields' as const,
19
+ info: {
20
+ fields: [
21
+ { name: `${prefix}_status`, value: '{{order.status}}', type: 'Custom Value' as const },
22
+ { name: `${prefix}_tracking`, value: '{{order.tracking}}', type: 'Custom Value' as const },
23
+ { name: `${prefix}_carrier`, value: '{{order.carrier}}', type: 'Custom Value' as const },
24
+ { name: `${prefix}_sku`, value: '{{order.sku}}', type: 'Custom Value' as const },
25
+ { name: `${prefix}_externalId`, value: '{{order.externalId}}', type: 'Custom Value' as const },
26
+ { name: `${prefix}_id`, value: '{{order.id}}', type: 'Custom Value' as const },
27
+ { name: `${prefix}_protocol`, value: '{{order.protocol}}', type: 'Custom Value' as const },
28
+ ],
29
+ },
30
+ })
31
+
32
+ // Block A: Direct EnduserOrder creation path
33
+ const direct_order_creation_block = async ({ sdk }: { sdk: Session }) => {
34
+ log_header("Block A: Direct EnduserOrder creation -> {{order.*}} in Set Fields")
35
+
36
+ const enduser = await sdk.api.endusers.createOne({})
37
+ const trigger = await sdk.api.automation_triggers.createOne({
38
+ title: TRIGGER_TITLE_BLOCK_A,
39
+ status: 'Active',
40
+ event: { type: 'Order Status Equals', info: { source: 'Beluga', status: 'Shipped' } },
41
+ action: buildSetFieldsAction('pharmacy'),
42
+ })
43
+
44
+ let createdOrderId: string | undefined
45
+ let nonMatchingOrderId: string | undefined
46
+
47
+ try {
48
+ const order = await sdk.api.enduser_orders.createOne({
49
+ enduserId: enduser.id,
50
+ source: 'Beluga',
51
+ status: 'Shipped',
52
+ title: 'Beluga Pharmacy Order',
53
+ externalId: 'EXT-A-123',
54
+ tracking: '1Z-AAA',
55
+ carrier: 'UPS',
56
+ sku: 'SKU-A',
57
+ protocol: 'wl1',
58
+ })
59
+ createdOrderId = order.id
60
+
61
+ await wait(undefined, 250) // allow trigger + Set Fields to run
62
+
63
+ await async_test(
64
+ "Block A: {{order.*}} templates resolve to literal values",
65
+ () => sdk.api.endusers.getOne(enduser.id),
66
+ { onResult: e => !!(
67
+ e.fields?.pharmacy_status === 'Shipped'
68
+ && e.fields?.pharmacy_tracking === '1Z-AAA'
69
+ && e.fields?.pharmacy_carrier === 'UPS'
70
+ && e.fields?.pharmacy_sku === 'SKU-A'
71
+ && e.fields?.pharmacy_externalId === 'EXT-A-123'
72
+ && e.fields?.pharmacy_protocol === 'wl1'
73
+ && typeof e.fields?.pharmacy_id === 'string'
74
+ && (e.fields?.pharmacy_id as string).length > 0
75
+ && (e.fields?.pharmacy_id as string) === order.id
76
+ )}
77
+ )
78
+
79
+ // Negative case: status that doesn't match the trigger should NOT alter fields
80
+ const beforeNonMatch = await sdk.api.endusers.getOne(enduser.id)
81
+ const snapshot = {
82
+ pharmacy_status: beforeNonMatch.fields?.pharmacy_status,
83
+ pharmacy_tracking: beforeNonMatch.fields?.pharmacy_tracking,
84
+ pharmacy_carrier: beforeNonMatch.fields?.pharmacy_carrier,
85
+ pharmacy_sku: beforeNonMatch.fields?.pharmacy_sku,
86
+ pharmacy_externalId: beforeNonMatch.fields?.pharmacy_externalId,
87
+ pharmacy_id: beforeNonMatch.fields?.pharmacy_id,
88
+ pharmacy_protocol: beforeNonMatch.fields?.pharmacy_protocol,
89
+ }
90
+
91
+ const nonMatching = await sdk.api.enduser_orders.createOne({
92
+ enduserId: enduser.id,
93
+ source: 'Beluga',
94
+ status: 'In Fulfillment', // does NOT match the trigger
95
+ title: 'Beluga Pharmacy Order (other status)',
96
+ externalId: 'EXT-A-NOMATCH',
97
+ tracking: 'NOPE',
98
+ carrier: 'NoCarrier',
99
+ sku: 'SKU-NOPE',
100
+ protocol: 'nope',
101
+ })
102
+ nonMatchingOrderId = nonMatching.id
103
+
104
+ await wait(undefined, 250)
105
+
106
+ await async_test(
107
+ "Block A: non-matching status leaves fields unchanged",
108
+ () => sdk.api.endusers.getOne(enduser.id),
109
+ { onResult: e => !!(
110
+ e.fields?.pharmacy_status === snapshot.pharmacy_status
111
+ && e.fields?.pharmacy_tracking === snapshot.pharmacy_tracking
112
+ && e.fields?.pharmacy_carrier === snapshot.pharmacy_carrier
113
+ && e.fields?.pharmacy_sku === snapshot.pharmacy_sku
114
+ && e.fields?.pharmacy_externalId === snapshot.pharmacy_externalId
115
+ && e.fields?.pharmacy_id === snapshot.pharmacy_id
116
+ && e.fields?.pharmacy_protocol === snapshot.pharmacy_protocol
117
+ )}
118
+ )
119
+ } finally {
120
+ if (createdOrderId) await sdk.api.enduser_orders.deleteOne(createdOrderId).catch(console.error)
121
+ if (nonMatchingOrderId) await sdk.api.enduser_orders.deleteOne(nonMatchingOrderId).catch(console.error)
122
+ await sdk.api.automation_triggers.deleteOne(trigger.id).catch(console.error)
123
+ await sdk.api.endusers.deleteOne(enduser.id).catch(console.error)
124
+ }
125
+ }
126
+
127
+ // Block B: Beluga webhook integration path
128
+ const beluga_webhook_block = async ({ sdk }: { sdk: Session }) => {
129
+ log_header("Block B: Beluga webhook -> {{order.*}} in Set Fields")
130
+
131
+ const webhookUrl = `${host}/v1/webhooks/beluga`
132
+ const externalOrderId = `EXT-B-${Date.now()}`
133
+
134
+ const enduser = await sdk.api.endusers.createOne({})
135
+ const form = await sdk.api.forms.createOne({ title: 'Order Templates Beluga Form' })
136
+ const formResponse = await sdk.api.form_responses.createOne({
137
+ formId: form.id,
138
+ enduserId: enduser.id,
139
+ formTitle: form.title,
140
+ })
141
+
142
+ const trigger = await sdk.api.automation_triggers.createOne({
143
+ title: TRIGGER_TITLE_BLOCK_B,
144
+ status: 'Active',
145
+ event: { type: 'Order Status Equals', info: { source: 'Beluga', status: 'Shipped' } },
146
+ action: buildSetFieldsAction('pharmacy'),
147
+ })
148
+
149
+ const deliveredTrigger = await sdk.api.automation_triggers.createOne({
150
+ title: `${TRIGGER_TITLE_BLOCK_B} (Delivered)`,
151
+ status: 'Active',
152
+ event: { type: 'Order Status Equals', info: { source: 'Beluga', status: 'Delivered' } },
153
+ action: buildSetFieldsAction('pharmacy'),
154
+ })
155
+
156
+ try {
157
+ // Step 1: PHARMACY_ORDER_SHIPPED
158
+ const shippedRes = await fetch(webhookUrl, {
159
+ method: 'POST',
160
+ headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({
162
+ masterId: `tellescope_${formResponse.id}`,
163
+ event: 'PHARMACY_ORDER_SHIPPED',
164
+ orderId: externalOrderId,
165
+ info: { carrier: 'FedEx', tracking: '7777-BBB' },
166
+ }),
167
+ })
168
+ assert(shippedRes.status === 200, `Beluga webhook (shipped) expected 200, got ${shippedRes.status}`)
169
+
170
+ await wait(undefined, 250) // webhook upsert + trigger + Set Fields
171
+
172
+ await async_test(
173
+ "Block B: Beluga PHARMACY_ORDER_SHIPPED resolves {{order.*}} into enduser fields",
174
+ () => sdk.api.endusers.getOne(enduser.id),
175
+ { onResult: e => !!(
176
+ e.fields?.pharmacy_status === 'Shipped'
177
+ && e.fields?.pharmacy_tracking === '7777-BBB'
178
+ && e.fields?.pharmacy_carrier === 'FedEx'
179
+ && e.fields?.pharmacy_externalId === externalOrderId
180
+ && typeof e.fields?.pharmacy_id === 'string'
181
+ && (e.fields?.pharmacy_id as string).length > 0
182
+ // Webhook does not set sku/protocol -> default branch in helper -> ''
183
+ && e.fields?.pharmacy_sku === ''
184
+ && e.fields?.pharmacy_protocol === ''
185
+ )}
186
+ )
187
+
188
+ // Step 2: PHARMACY_ORDER_DELIVERED for the same order
189
+ const deliveredRes = await fetch(webhookUrl, {
190
+ method: 'POST',
191
+ headers: { 'Content-Type': 'application/json' },
192
+ body: JSON.stringify({
193
+ masterId: `tellescope_${formResponse.id}`,
194
+ event: 'PHARMACY_ORDER_DELIVERED',
195
+ orderId: externalOrderId,
196
+ }),
197
+ })
198
+ assert(deliveredRes.status === 200, `Beluga webhook (delivered) expected 200, got ${deliveredRes.status}`)
199
+
200
+ await wait(undefined, 250)
201
+
202
+ await async_test(
203
+ "Block B: PHARMACY_ORDER_DELIVERED flips pharmacy_status to 'Delivered'",
204
+ () => sdk.api.endusers.getOne(enduser.id),
205
+ { onResult: e => !!(
206
+ e.fields?.pharmacy_status === 'Delivered'
207
+ && e.fields?.pharmacy_externalId === externalOrderId
208
+ )}
209
+ )
210
+ } finally {
211
+ // Clean up the order created by the webhook
212
+ try {
213
+ const orders = await sdk.api.enduser_orders.getSome({
214
+ filter: { source: 'Beluga', externalId: externalOrderId } as any,
215
+ })
216
+ for (const o of orders) {
217
+ await sdk.api.enduser_orders.deleteOne(o.id).catch(console.error)
218
+ }
219
+ } catch (err) {
220
+ console.error(err)
221
+ }
222
+
223
+ await sdk.api.automation_triggers.deleteOne(trigger.id).catch(console.error)
224
+ await sdk.api.automation_triggers.deleteOne(deliveredTrigger.id).catch(console.error)
225
+ await sdk.api.form_responses.deleteOne(formResponse.id).catch(console.error)
226
+ await sdk.api.forms.deleteOne(form.id).catch(console.error)
227
+ await sdk.api.endusers.deleteOne(enduser.id).catch(console.error)
228
+ }
229
+ }
230
+
231
+ export const set_fields_order_templates_tests = async (
232
+ { sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }
233
+ ) => {
234
+ log_header("Set Fields: {{order.*}} template resolution")
235
+ await direct_order_creation_block({ sdk })
236
+ await beluga_webhook_block({ sdk })
237
+ }
238
+
239
+ if (require.main === module) {
240
+ console.log(`Using API URL: ${host}`)
241
+ const sdk = new Session({ host })
242
+ const sdkNonAdmin = new Session({ host })
243
+
244
+ const runTests = async () => {
245
+ await setup_tests(sdk, sdkNonAdmin)
246
+ await set_fields_order_templates_tests({ sdk, sdkNonAdmin })
247
+ }
248
+
249
+ runTests()
250
+ .then(() => {
251
+ console.log("set_fields_order_templates test suite completed successfully")
252
+ process.exit(0)
253
+ })
254
+ .catch((error) => {
255
+ console.error("set_fields_order_templates test suite failed:", error)
256
+ process.exit(1)
257
+ })
258
+ }
@@ -37,9 +37,16 @@ export const setup_tests = async (sdk: Session, sdkNonAdmin: Session) => {
37
37
  // Authenticate the SDKs first
38
38
  await sdk.authenticate(email, password)
39
39
  await sdkNonAdmin.authenticate(nonAdminEmail, nonAdminPassword)
40
-
40
+
41
41
  await async_test('test_authenticated', sdk.test_authenticated, { expectedResult: 'Authenticated!' })
42
42
 
43
+ // Defensive: clear residual OTP gating from a previously-aborted run of
44
+ // enduser_session_invalidation_tests, which would otherwise cause enduser
45
+ // tokens minted in subsequent tests to be rejected as Unauthenticated.
46
+ await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
47
+ portalSettings: { authentication: { requireOTP: false, requireOTPAfterPassword: false } },
48
+ }, { replaceObjectFields: true })
49
+
43
50
  await async_test(
44
51
  'test_authenticated (with API Key)',
45
52
  (new Session({ host, apiKey: '3n5q0SCBT_iUvZz-b9BJtX7o7HQUVJ9v132PgHJNJsg.' /* local test key */ })).test_authenticated,
@@ -49,6 +49,7 @@ import { purchase_made_trigger_tests } from "./api_tests/purchase_made_trigger.t
49
49
  import { appointment_rescheduled_trigger_tests } from "./api_tests/appointment_rescheduled_trigger.test"
50
50
  import { journey_error_branching_tests } from "./api_tests/journey_error_branching.test"
51
51
  import { afteraction_day_of_month_delay_tests } from "./api_tests/afteraction_day_of_month_delay.test"
52
+ import { push_forms_to_portal_group_completion_tests } from "./api_tests/push_forms_to_portal_group_completion.test"
52
53
  import { setup_tests } from "./setup"
53
54
  import { evaluate_conditional_logic_for_enduser_fields, FORM_LOGIC_CALCULATED_FIELDS, get_care_team_primary, get_flattened_fields, get_next_reminder_timestamp, object_is_empty, replace_enduser_template_values, replace_form_field_template_values, responses_satisfy_conditions, truncate_string, weighted_round_robin, YYYY_MM_DD_to_MM_DD_YYYY } from "@tellescope/utilities"
54
55
  import { DEFAULT_OPERATIONS, PLACEHOLDER_ID, ZENDESK_INTEGRATIONS_TITLE, ZOOM_TITLE } from "@tellescope/constants"
@@ -74,10 +75,12 @@ import {
74
75
 
75
76
  import fs from "fs"
76
77
  import { load_inbox_data_tests } from "./api_tests/load_inbox_data.test";
78
+ import { enduser_login_tests } from "./api_tests/enduser_login.test";
77
79
  import { eom_procedure_codes_tests } from "./api_tests/eom_procedure_codes.test";
78
80
  import { cross_org_api_key_tests } from "./api_tests/cross_org_api_key.test";
79
81
  import { custom_dashboards_tests } from "./api_tests/custom_dashboards.test";
80
82
  import { message_assignment_trigger_tests } from "./api_tests/message_assignment_trigger.test";
83
+ import { outbound_chat_sent_trigger_tests } from "./api_tests/outbound_chat_sent_trigger.test";
81
84
  import { time_tracks_tests, time_tracks_historical_tests, time_tracks_correction_tests, time_tracks_review_tests, time_tracks_lock_tests, time_tracks_edge_case_tests } from "./api_tests/time_tracks.test";
82
85
  import { monthly_availability_restrictions_tests } from "./api_tests/monthly_availability_restrictions.test";
83
86
  import { calendar_event_limits_tests } from "./api_tests/calendar_event_limits.test";
@@ -95,11 +98,14 @@ import { load_team_chat_tests } from "./api_tests/load_team_chat.test";
95
98
  import { form_started_trigger_tests } from "./api_tests/form_started_trigger.test";
96
99
  import { form_submitted_trigger_tests } from "./api_tests/form_submitted_trigger.test";
97
100
  import { medication_added_trigger_tests } from "./api_tests/medication_added_trigger.test";
101
+ import { conditional_logic_medication_unit_tests } from "./unit_tests/conditional_logic_medication.test";
98
102
  import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
99
103
  import { organization_settings_duplicates_tests } from "./api_tests/organization_settings_duplicates.test";
100
104
  import { calendar_events_bulk_update_tests } from "./api_tests/calendar_events_bulk_update.test";
101
105
  import { openloop_webhooks_tests } from "./api_tests/openloop_webhooks.test";
102
106
  import { beluga_pharmacy_mappings_tests } from "./api_tests/beluga_pharmacy_mappings.test";
107
+ import { account_switcher_tests } from "./api_tests/account_switcher.test";
108
+ import { set_fields_order_templates_tests } from "./api_tests/set_fields_order_templates.test";
103
109
  import { date_string_validation_tests } from "./api_tests/date_string_validation.test";
104
110
  import { enduser_session_invalidation_tests } from "./api_tests/enduser_session_invalidation.test";
105
111
  import { enduser_cross_access_isolation_tests } from "./api_tests/enduser_cross_access_isolation.test";
@@ -5087,6 +5093,7 @@ const automation_trigger_tests = async () => {
5087
5093
  log_header("Automation Trigger Tests")
5088
5094
 
5089
5095
  await order_status_equals_tests()
5096
+ await set_fields_order_templates_tests({ sdk, sdkNonAdmin })
5090
5097
  await medication_added_trigger_tests({ sdk, sdkNonAdmin })
5091
5098
  await appointment_cancelled_tests()
5092
5099
  await set_fields_tests()
@@ -5095,6 +5102,7 @@ const automation_trigger_tests = async () => {
5095
5102
  await form_response_set_fields_trigger_tests()
5096
5103
  await form_response_set_fields_journey_tests()
5097
5104
  await appointment_completed_trigger_tests({ sdk, sdkNonAdmin })
5105
+ await push_forms_to_portal_group_completion_tests({ sdk, sdkNonAdmin })
5098
5106
  await trigger_events_api_tests()
5099
5107
  await fields_changed_tests()
5100
5108
  await field_equals_trigger_tests()
@@ -7119,16 +7127,18 @@ const merge_enduser_tests = async () => {
7119
7127
  const stripeCustomerId = 'example_cu_id';
7120
7128
  const stripeKey = 'example_stripe_key';
7121
7129
  const [source, destination, otherEnduser] = (await sdk.api.endusers.createSome([
7122
- {
7123
- email: 'source@tellescope.com', fname: 'source', lname: 'enduser',
7124
- references: [{ type: '2', id: '2.2' }, { type: '3', id: '3.2' }, { type: '4', id: '4.2'}],
7130
+ {
7131
+ email: 'source@tellescope.com', fname: 'source', lname: 'enduser',
7132
+ references: [{ type: '2', id: '2.2' }, { type: '3', id: '3.2' }, { type: '4', id: '4.2'}],
7125
7133
  // @ts-ignore
7126
7134
  stripeCustomerId,
7127
7135
  stripeKey,
7128
7136
  athenaPracticeId: '12345',
7129
7137
  athenaDepartmentId: '54321',
7138
+ insurance: { memberId: 'src-member', payerName: 'Source Payer' },
7139
+ insuranceSecondary: { memberId: 'src-secondary' },
7130
7140
  },
7131
- { email: 'destination@tellescope.com', source: '4', externalId: "4", references: [{ type: '1', id: '1' }, { type: '2', id: '2' }] },
7141
+ { email: 'destination@tellescope.com', source: '4', externalId: "4", references: [{ type: '1', id: '1' }, { type: '2', id: '2' }], insurance: { memberId: 'dest-member', payerName: 'Dest Payer' } },
7132
7142
  { email: 'other@tellescope.com'},
7133
7143
  ])).created
7134
7144
 
@@ -7177,7 +7187,10 @@ const merge_enduser_tests = async () => {
7177
7187
  && e.references?.find(r => r.type === '1')?.id === '1'
7178
7188
  && e.references?.find(r => r.type === '2')?.id === '2'
7179
7189
  && e.references?.find(r => r.type === '3')?.id === '3.2'
7180
- && !e.references?.find(r => r.type === '4')?.id
7190
+ && !e.references?.find(r => r.type === '4')?.id
7191
+ && e.insurance?.memberId === 'dest-member'
7192
+ && e.insurance?.payerName === 'Dest Payer'
7193
+ && e.insuranceSecondary?.memberId === 'src-secondary'
7181
7194
  )}
7182
7195
  )
7183
7196
 
@@ -14303,10 +14316,15 @@ const ip_address_form_tests = async () => {
14303
14316
 
14304
14317
 
14305
14318
  await enduser_conditional_logic_tests()
14319
+ await conditional_logic_medication_unit_tests()
14306
14320
  await replace_enduser_template_values_tests()
14307
14321
  await replace_form_field_template_values_tests()
14308
14322
  await mfa_tests()
14309
14323
  await setup_tests(sdk, sdkNonAdmin)
14324
+ await account_switcher_tests({ sdk, sdkNonAdmin })
14325
+ await enduser_login_tests({ sdk, sdkNonAdmin })
14326
+ await outbound_chat_sent_trigger_tests({ sdk })
14327
+ await automation_trigger_tests()
14310
14328
  await enduser_cross_access_isolation_tests({ sdk, sdkNonAdmin })
14311
14329
  await eom_procedure_codes_tests({ sdk, sdkNonAdmin })
14312
14330
  await cross_org_api_key_tests({ sdk, sdkNonAdmin })
@@ -14317,7 +14335,6 @@ const ip_address_form_tests = async () => {
14317
14335
  await form_submitted_trigger_tests({ sdk, sdkNonAdmin })
14318
14336
  await date_string_validation_tests({ sdk, sdkNonAdmin })
14319
14337
  await openloop_webhooks_tests({ sdk, sdkNonAdmin })
14320
- await automation_trigger_tests()
14321
14338
  await integrations_redacted_tests({ sdk, sdkNonAdmin })
14322
14339
  await mdb_sort_tests({ sdk, sdkNonAdmin })
14323
14340
  await search_tests()
@@ -0,0 +1,133 @@
1
+ import {
2
+ evaluate_conditional_logic_for_medication_title,
3
+ evaluate_string_field_comparison,
4
+ } from "@tellescope/utilities"
5
+ import { CompoundFilter } from "@tellescope/types-models"
6
+
7
+ let failures = 0
8
+ let passed = 0
9
+
10
+ const assert = (name: string, actual: boolean, expected: boolean) => {
11
+ if (actual === expected) {
12
+ passed++
13
+ console.log(` ✓ ${name}`)
14
+ } else {
15
+ failures++
16
+ console.error(` ✗ ${name} — expected ${expected}, got ${actual}`)
17
+ }
18
+ }
19
+
20
+ const run_string_comparison_tests = () => {
21
+ console.log("\n[evaluate_string_field_comparison]")
22
+
23
+ // Plain-string equals (implicit operator)
24
+ assert("equals plain string match", evaluate_string_field_comparison('Aspirin', 'Aspirin'), true)
25
+ assert("equals plain string mismatch", evaluate_string_field_comparison('Aspirin', 'Lisinopril'), false)
26
+ assert("equals empty string against empty title", evaluate_string_field_comparison('', ''), true)
27
+ assert("equals empty string against undefined title", evaluate_string_field_comparison(undefined, ''), true)
28
+
29
+ // $ne
30
+ assert("$ne matches when different", evaluate_string_field_comparison('Aspirin', { $ne: 'Lisinopril' }), true)
31
+ assert("$ne fails when equal", evaluate_string_field_comparison('Aspirin', { $ne: 'Aspirin' }), false)
32
+
33
+ // $contains (case sensitive — consistent with enduser/form-response evaluators)
34
+ assert("$contains substring match", evaluate_string_field_comparison('Semaglutide GLP-1', { $contains: 'GLP' }), true)
35
+ assert("$contains case sensitive miss", evaluate_string_field_comparison('Lisinopril 10MG', { $contains: 'mg' }), false)
36
+ assert("$contains case sensitive match", evaluate_string_field_comparison('Lisinopril 10mg', { $contains: 'mg' }), true)
37
+ assert("$contains no match", evaluate_string_field_comparison('Aspirin', { $contains: 'GLP' }), false)
38
+ assert("$contains empty string is always true", evaluate_string_field_comparison('Aspirin', { $contains: '' }), true)
39
+
40
+ // $doesNotContain
41
+ assert("$doesNotContain mismatch true", evaluate_string_field_comparison('Aspirin', { $doesNotContain: 'GLP' }), true)
42
+ assert("$doesNotContain match false", evaluate_string_field_comparison('Semaglutide', { $doesNotContain: 'Sema' }), false)
43
+ assert("$doesNotContain case sensitive (different case = no match)", evaluate_string_field_comparison('Placebo', { $doesNotContain: 'PLACEBO' }), true)
44
+
45
+ // $exists
46
+ assert("$exists:true on present", evaluate_string_field_comparison('Aspirin', { $exists: true }), true)
47
+ assert("$exists:true on empty", evaluate_string_field_comparison('', { $exists: true }), false)
48
+ assert("$exists:true on undefined", evaluate_string_field_comparison(undefined, { $exists: true }), false)
49
+ assert("$exists:false on present", evaluate_string_field_comparison('Aspirin', { $exists: false }), false)
50
+ assert("$exists:false on undefined", evaluate_string_field_comparison(undefined, { $exists: false }), true)
51
+
52
+ // null operator → treated as "no value set"
53
+ assert("null operator on undefined title", evaluate_string_field_comparison(undefined, null), true)
54
+ assert("null operator on present title", evaluate_string_field_comparison('Aspirin', null), false)
55
+
56
+ // Unknown operator → returns true (don't suppress triggers on bad data)
57
+ assert("unknown operator returns true", evaluate_string_field_comparison('Aspirin', { $weirdOp: 'x' } as any), true)
58
+ }
59
+
60
+ const run_conditional_logic_tests = () => {
61
+ console.log("\n[evaluate_conditional_logic_for_medication_title]")
62
+
63
+ // Single condition (case-sensitive)
64
+ const c1: CompoundFilter<'title'> = { condition: { title: { $contains: 'GLP' } } }
65
+ assert("single $contains true", evaluate_conditional_logic_for_medication_title('Semaglutide GLP-1', c1), true)
66
+ assert("single $contains false", evaluate_conditional_logic_for_medication_title('Aspirin', c1), false)
67
+ assert("single $contains case-sensitive miss", evaluate_conditional_logic_for_medication_title('Semaglutide glp-1', c1), false)
68
+
69
+ // Plain string default-equals
70
+ const c2: CompoundFilter<'title'> = { condition: { title: 'Aspirin' } }
71
+ assert("plain equals true", evaluate_conditional_logic_for_medication_title('Aspirin', c2), true)
72
+ assert("plain equals false", evaluate_conditional_logic_for_medication_title('Lisinopril', c2), false)
73
+
74
+ // $and (both true)
75
+ const cAnd: CompoundFilter<'title'> = {
76
+ $and: [
77
+ { condition: { title: { $contains: 'mg' } } },
78
+ { condition: { title: { $doesNotContain: 'Placebo' } } },
79
+ ],
80
+ }
81
+ assert("$and both pass", evaluate_conditional_logic_for_medication_title('Lisinopril 10mg', cAnd), true)
82
+ assert("$and first fails", evaluate_conditional_logic_for_medication_title('Lisinopril', cAnd), false)
83
+ assert("$and second fails", evaluate_conditional_logic_for_medication_title('Placebo 5mg', cAnd), false)
84
+
85
+ // $or
86
+ const cOr: CompoundFilter<'title'> = {
87
+ $or: [
88
+ { condition: { title: 'Aspirin' } },
89
+ { condition: { title: { $contains: 'pril' } } },
90
+ ],
91
+ }
92
+ assert("$or first matches", evaluate_conditional_logic_for_medication_title('Aspirin', cOr), true)
93
+ assert("$or second matches", evaluate_conditional_logic_for_medication_title('Lisinopril', cOr), true)
94
+ assert("$or neither matches", evaluate_conditional_logic_for_medication_title('Metformin', cOr), false)
95
+
96
+ // Mixed compound: $and containing $or — the canonical "compound" case
97
+ const cMixed: CompoundFilter<'title'> = {
98
+ $and: [
99
+ { condition: { title: { $contains: 'mg' } } },
100
+ {
101
+ $or: [
102
+ { condition: { title: { $contains: 'Lisin' } } },
103
+ { condition: { title: { $contains: 'Metform' } } },
104
+ ],
105
+ },
106
+ ],
107
+ }
108
+ assert("$and+$or first or-branch matches", evaluate_conditional_logic_for_medication_title('Lisinopril 10mg', cMixed), true)
109
+ assert("$and+$or second or-branch matches", evaluate_conditional_logic_for_medication_title('Metformin 500mg', cMixed), true)
110
+ assert("$and+$or and-branch fails", evaluate_conditional_logic_for_medication_title('Lisinopril', cMixed), false)
111
+ assert("$and+$or or-branch fails", evaluate_conditional_logic_for_medication_title('Aspirin 81mg', cMixed), false)
112
+
113
+ // Empty / no-op
114
+ assert("empty conditions returns true", evaluate_conditional_logic_for_medication_title('Aspirin', {}), true)
115
+
116
+ // Unknown operator inside condition → returns true (safe)
117
+ const cUnknown: CompoundFilter<'title'> = { condition: { title: { $regex: 'foo' } as any } }
118
+ assert("unknown operator is permissive", evaluate_conditional_logic_for_medication_title('Aspirin', cUnknown), true)
119
+ }
120
+
121
+ const run_all = () => {
122
+ console.log("Running conditional_logic_medication unit tests")
123
+ run_string_comparison_tests()
124
+ run_conditional_logic_tests()
125
+ console.log(`\nResults: ${passed} passed, ${failures} failed`)
126
+ if (failures > 0) process.exit(1)
127
+ }
128
+
129
+ if (require.main === module) {
130
+ run_all()
131
+ }
132
+
133
+ export { run_all as conditional_logic_medication_unit_tests }
Binary file