@tellescope/sdk 1.255.12 → 1.255.14

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 (63) hide show
  1. package/lib/cjs/sdk.d.ts +5 -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/calendar_event_references_update.test.d.ts +14 -0
  6. package/lib/cjs/tests/api_tests/calendar_event_references_update.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/calendar_event_references_update.test.js +172 -0
  8. package/lib/cjs/tests/api_tests/calendar_event_references_update.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js +179 -0
  12. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/phone_tree_enduser_condition.test.d.ts +6 -0
  14. package/lib/cjs/tests/api_tests/phone_tree_enduser_condition.test.d.ts.map +1 -0
  15. package/lib/cjs/tests/api_tests/phone_tree_enduser_condition.test.js +237 -0
  16. package/lib/cjs/tests/api_tests/phone_tree_enduser_condition.test.js.map +1 -0
  17. package/lib/cjs/tests/api_tests/send_message_model.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/send_message_model.test.js.map +1 -0
  19. package/lib/cjs/tests/api_tests/totp_mfa.test.d.ts +6 -0
  20. package/lib/cjs/tests/api_tests/totp_mfa.test.d.ts.map +1 -0
  21. package/lib/cjs/tests/api_tests/totp_mfa.test.js +518 -0
  22. package/lib/cjs/tests/api_tests/totp_mfa.test.js.map +1 -0
  23. package/lib/cjs/tests/tests.d.ts.map +1 -1
  24. package/lib/cjs/tests/tests.js +161 -145
  25. package/lib/cjs/tests/tests.js.map +1 -1
  26. package/lib/esm/enduser.d.ts +0 -1
  27. package/lib/esm/enduser.d.ts.map +1 -1
  28. package/lib/esm/sdk.d.ts +7 -3
  29. package/lib/esm/sdk.d.ts.map +1 -1
  30. package/lib/esm/sdk.js +3 -0
  31. package/lib/esm/sdk.js.map +1 -1
  32. package/lib/esm/session.d.ts +0 -1
  33. package/lib/esm/session.d.ts.map +1 -1
  34. package/lib/esm/tests/api_tests/calendar_event_references_update.test.d.ts +14 -0
  35. package/lib/esm/tests/api_tests/calendar_event_references_update.test.d.ts.map +1 -0
  36. package/lib/esm/tests/api_tests/calendar_event_references_update.test.js +168 -0
  37. package/lib/esm/tests/api_tests/calendar_event_references_update.test.js.map +1 -0
  38. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.d.ts +6 -0
  39. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -0
  40. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js +175 -0
  41. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -0
  42. package/lib/esm/tests/api_tests/phone_tree_enduser_condition.test.d.ts +6 -0
  43. package/lib/esm/tests/api_tests/phone_tree_enduser_condition.test.d.ts.map +1 -0
  44. package/lib/esm/tests/api_tests/phone_tree_enduser_condition.test.js +233 -0
  45. package/lib/esm/tests/api_tests/phone_tree_enduser_condition.test.js.map +1 -0
  46. package/lib/esm/tests/api_tests/send_message_model.test.d.ts.map +1 -0
  47. package/lib/esm/tests/api_tests/send_message_model.test.js.map +1 -0
  48. package/lib/esm/tests/api_tests/totp_mfa.test.d.ts +6 -0
  49. package/lib/esm/tests/api_tests/totp_mfa.test.d.ts.map +1 -0
  50. package/lib/esm/tests/api_tests/totp_mfa.test.js +511 -0
  51. package/lib/esm/tests/api_tests/totp_mfa.test.js.map +1 -0
  52. package/lib/esm/tests/tests.d.ts.map +1 -1
  53. package/lib/esm/tests/tests.js +161 -145
  54. package/lib/esm/tests/tests.js.map +1 -1
  55. package/lib/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +10 -10
  57. package/src/sdk.ts +12 -0
  58. package/src/tests/api_tests/calendar_event_references_update.test.ts +108 -0
  59. package/src/tests/api_tests/phone_calls_cold_transfer.test.ts +113 -0
  60. package/src/tests/api_tests/phone_tree_enduser_condition.test.ts +174 -0
  61. package/src/tests/api_tests/totp_mfa.test.ts +385 -0
  62. package/src/tests/tests.ts +8 -0
  63. package/test_generated.pdf +0 -0
package/src/sdk.ts CHANGED
@@ -435,6 +435,12 @@ type Queries = { [K in keyof ClientModelForName]: APIQuery<K> } & {
435
435
  configure_MFA: (args: extractFields<CustomActions['users']['configure_MFA']['parameters']>) => (
436
436
  Promise<extractFields<CustomActions['users']['configure_MFA']['returns']>>
437
437
  ),
438
+ begin_TOTP_configuration: (args: extractFields<CustomActions['users']['begin_TOTP_configuration']['parameters']>) => (
439
+ Promise<extractFields<CustomActions['users']['begin_TOTP_configuration']['returns']>>
440
+ ),
441
+ confirm_TOTP_configuration: (args: extractFields<CustomActions['users']['confirm_TOTP_configuration']['parameters']>) => (
442
+ Promise<extractFields<CustomActions['users']['confirm_TOTP_configuration']['returns']>>
443
+ ),
438
444
  generate_MFA_challenge: (args: extractFields<CustomActions['users']['generate_MFA_challenge']['parameters']>) => (
439
445
  Promise<extractFields<CustomActions['users']['generate_MFA_challenge']['returns']>>
440
446
  ),
@@ -698,6 +704,9 @@ type Queries = { [K in keyof ClientModelForName]: APIQuery<K> } & {
698
704
  upgrade_to_conference: (args: extractFields<CustomActions['phone_calls']['upgrade_to_conference']['parameters']>) => (
699
705
  Promise<extractFields<CustomActions['phone_calls']['upgrade_to_conference']['returns']>>
700
706
  ),
707
+ cold_transfer: (args: extractFields<CustomActions['phone_calls']['cold_transfer']['parameters']>) => (
708
+ Promise<extractFields<CustomActions['phone_calls']['cold_transfer']['returns']>>
709
+ ),
701
710
  add_conference_attendees: (args: extractFields<CustomActions['phone_calls']['add_conference_attendees']['parameters']>) => (
702
711
  Promise<extractFields<CustomActions['phone_calls']['add_conference_attendees']['returns']>>
703
712
  ),
@@ -1025,6 +1034,8 @@ export class Session extends SessionManager {
1025
1034
  queries.users.request_linked_account_access = a => this._POST(`/v1/${schema.users.customActions.request_linked_account_access.path}`, a)
1026
1035
 
1027
1036
  queries.users.configure_MFA = a => this._POST(`/v1/${schema.users.customActions.configure_MFA.path}`, a)
1037
+ queries.users.begin_TOTP_configuration = a => this._POST(`/v1/${schema.users.customActions.begin_TOTP_configuration.path}`, a)
1038
+ queries.users.confirm_TOTP_configuration = a => this._POST(`/v1/${schema.users.customActions.confirm_TOTP_configuration.path}`, a)
1028
1039
  queries.users.generate_MFA_challenge = a => this._POST(`/v1/${schema.users.customActions.generate_MFA_challenge.path}`, a)
1029
1040
  queries.users.submit_MFA_challenge = a => this._POST(`/v1/${schema.users.customActions.submit_MFA_challenge.path}`, a)
1030
1041
 
@@ -1146,6 +1157,7 @@ export class Session extends SessionManager {
1146
1157
  queries.phone_calls.get_report = a => this._GET(`/v1/${schema.phone_calls.customActions.get_report.path}`, a)
1147
1158
  queries.phone_calls.authenticate_calling = a => this._POST(`/v1${schema.phone_calls.customActions.authenticate_calling.path}`, a)
1148
1159
  queries.phone_calls.upgrade_to_conference = a => this._POST(`/v1${schema.phone_calls.customActions.upgrade_to_conference.path}`, a)
1160
+ queries.phone_calls.cold_transfer = a => this._POST(`/v1${schema.phone_calls.customActions.cold_transfer.path}`, a)
1149
1161
  queries.phone_calls.add_conference_attendees = a => this._POST(`/v1${schema.phone_calls.customActions.add_conference_attendees.path}`, a)
1150
1162
  queries.phone_calls.remove_conference_attendees = a => this._POST(`/v1${schema.phone_calls.customActions.remove_conference_attendees.path}`, a)
1151
1163
  queries.phone_calls.end_conference = a => this._POST(`/v1${schema.phone_calls.customActions.end_conference.path}`, a)
@@ -0,0 +1,108 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session, EnduserSession } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ log_header,
7
+ } from "@tellescope/testing"
8
+ import { setup_tests, authenticate_enduser_via_token } from "../setup"
9
+
10
+ const host = process.env.API_URL || 'http://localhost:8080' as const
11
+ const businessId = '60398b1131a295e64f084ff6'
12
+
13
+ /**
14
+ * CU-86e2dvrn9: calendar_events.references switched from updatesDisabled to
15
+ * enduserUpdatesDisabled (schema.ts), mirroring the Enduser references behavior:
16
+ * - staff / API-key sessions may update references (e.g. to set or clear
17
+ * external appointment IDs like stale Elation references)
18
+ * - enduser (patient) sessions remain blocked — required because endusers DO
19
+ * have update access on calendar_events they attend (for cancelling)
20
+ */
21
+ export const calendar_event_references_update_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
22
+ log_header("Calendar Event References Update (enduserUpdatesDisabled)")
23
+
24
+ let enduserId: string | undefined
25
+ let eventId: string | undefined
26
+
27
+ try {
28
+ // Setup: enduser attending a calendar event, so the enduser session has update access to it
29
+ const enduser = await sdk.api.endusers.createOne({
30
+ email: `references-update-${Date.now()}@example.com`,
31
+ fname: 'References',
32
+ lname: 'UpdateTest',
33
+ })
34
+ enduserId = enduser.id
35
+
36
+ const event = await sdk.api.calendar_events.createOne({
37
+ title: "References Update Test Event",
38
+ durationInMinutes: 30,
39
+ startTimeInMS: Date.now(),
40
+ attendees: [{ id: enduser.id, type: 'enduser' }],
41
+ })
42
+ eventId = event.id
43
+
44
+ const enduserSDK = new EnduserSession({ host, businessId })
45
+ await authenticate_enduser_via_token(sdk, enduserSDK, { id: enduser.id })
46
+
47
+ // ---- Staff/API session can update references ----
48
+ const staffReferences = [{ type: 'Elation', id: 'elation-appointment-123' }]
49
+ await async_test(
50
+ 'staff can update calendar_events.references',
51
+ () => sdk.api.calendar_events.updateOne(event.id, { references: staffReferences }),
52
+ { onResult: e => e.references?.length === 1 && e.references[0].type === 'Elation' && e.references[0].id === 'elation-appointment-123' },
53
+ )
54
+ await async_test(
55
+ 'staff references update persisted',
56
+ () => sdk.api.calendar_events.getOne(event.id),
57
+ { onResult: e => e.references?.length === 1 && e.references[0].type === 'Elation' && e.references[0].id === 'elation-appointment-123' },
58
+ )
59
+
60
+ // ---- Enduser session (attendee with update access) is still blocked on references ----
61
+ await async_test(
62
+ 'enduser cannot update calendar_events.references',
63
+ () => enduserSDK.api.calendar_events.updateOne(event.id, { references: [{ type: 'Attacker', id: 'attacker-reference' }] } as any),
64
+ { shouldError: true, onError: e => e.message === "references cannot be updated by endusers" },
65
+ )
66
+ await async_test(
67
+ 'rejected enduser references update did not persist',
68
+ () => sdk.api.calendar_events.getOne(event.id),
69
+ { onResult: e => e.references?.length === 1 && e.references[0].type === 'Elation' && e.references[0].id === 'elation-appointment-123' },
70
+ )
71
+
72
+ // ---- Positive control: the guard is field-scoped — enduser can still cancel the event ----
73
+ await async_test(
74
+ 'enduser can still perform allowed update (cancelledAt)',
75
+ () => enduserSDK.api.calendar_events.updateOne(event.id, { cancelledAt: new Date() }),
76
+ { onResult: e => !!e.cancelledAt },
77
+ )
78
+ } finally {
79
+ if (eventId) {
80
+ try { await sdk.api.calendar_events.deleteOne(eventId) } catch {}
81
+ }
82
+ if (enduserId) {
83
+ try { await sdk.api.endusers.deleteOne(enduserId) } catch {}
84
+ }
85
+ }
86
+ }
87
+
88
+ // Allow running this test file independently
89
+ if (require.main === module) {
90
+ console.log(`🌐 Using API URL: ${host}`)
91
+ const sdk = new Session({ host })
92
+ const sdkNonAdmin = new Session({ host })
93
+
94
+ const runTests = async () => {
95
+ await setup_tests(sdk, sdkNonAdmin)
96
+ await calendar_event_references_update_tests({ sdk, sdkNonAdmin })
97
+ }
98
+
99
+ runTests()
100
+ .then(() => {
101
+ console.log("✅ Calendar event references update test suite completed successfully")
102
+ process.exit(0)
103
+ })
104
+ .catch((error) => {
105
+ console.error("❌ Calendar event references update test suite failed:", error)
106
+ process.exit(1)
107
+ })
108
+ }
@@ -0,0 +1,113 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ handleAnyError,
7
+ log_header,
8
+ } from "@tellescope/testing"
9
+ import { setup_tests } from "../setup"
10
+
11
+ const host = process.env.API_URL || 'http://localhost:8080' as const
12
+
13
+ const NONEXISTENT_MONGO_ID = '60398b1131a295e64f084fff'
14
+
15
+ // Validation-path tests for the cold transfer endpoint. All assertions below hit
16
+ // validations that run before any Twilio access, so no Twilio config is needed.
17
+ export const phone_calls_cold_transfer_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
18
+ log_header("Phone Calls Cold Transfer Tests")
19
+
20
+ const enduser = await sdk.api.endusers.createOne({ fname: 'Cold', lname: 'Transfer' })
21
+ const targetUsers: { id: string }[] = []
22
+
23
+ try {
24
+ const inboundCall = await sdk.api.phone_calls.createOne({
25
+ enduserId: enduser.id,
26
+ inbound: true,
27
+ externalId: 'CAcoldtransfertestinbound000000001',
28
+ from: '+15550001111',
29
+ to: '+15550002222',
30
+ userId: sdk.userInfo.id,
31
+ })
32
+ const outboundCall = await sdk.api.phone_calls.createOne({
33
+ enduserId: enduser.id,
34
+ inbound: false,
35
+ externalId: 'CAcoldtransfertestoutbound00000001',
36
+ from: '+15550002222',
37
+ to: '+15550001111',
38
+ userId: sdk.userInfo.id,
39
+ })
40
+
41
+ // throwaway target users (never modify existing users' roles)
42
+ const disabledTargetUser = await sdk.api.users.createOne({
43
+ email: 'cold.transfer.disabled@tellescope.com',
44
+ verifiedEmail: true,
45
+ notificationEmailsDisabled: true,
46
+ disableIncomingPhoneCalls: true,
47
+ })
48
+ targetUsers.push(disabledTargetUser)
49
+
50
+ await async_test(
51
+ "cold_transfer: cannot transfer to yourself",
52
+ () => sdk.api.phone_calls.cold_transfer({ callSid: inboundCall.externalId, targetUserId: sdk.userInfo.id }),
53
+ { shouldError: true, onError: (e: any) => e.message === "Cannot transfer a call to yourself" }
54
+ )
55
+ await async_test(
56
+ "cold_transfer: bogus callSid rejected",
57
+ () => sdk.api.phone_calls.cold_transfer({ callSid: 'CAdoesnotexist0000000000000000000', targetUserId: disabledTargetUser.id }),
58
+ { shouldError: true, onError: (e: any) => e.message === "Call not found" }
59
+ )
60
+ await async_test(
61
+ "cold_transfer: outbound calls rejected",
62
+ () => sdk.api.phone_calls.cold_transfer({ callSid: outboundCall.externalId, targetUserId: disabledTargetUser.id }),
63
+ { shouldError: true, onError: (e: any) => e.message === "Only inbound calls can be transferred" }
64
+ )
65
+ // conferenceId is not settable via the public API (only set internally by conference webhooks),
66
+ // so the "Conference calls cannot be transferred" branch is covered by manual E2E testing only
67
+ await async_test(
68
+ "cold_transfer: nonexistent target user rejected",
69
+ () => sdk.api.phone_calls.cold_transfer({ callSid: inboundCall.externalId, targetUserId: NONEXISTENT_MONGO_ID }),
70
+ { shouldError: true, onError: (e: any) => e.message === "Target user not found" }
71
+ )
72
+ await async_test(
73
+ "cold_transfer: target user with incoming calls disabled rejected",
74
+ () => sdk.api.phone_calls.cold_transfer({ callSid: inboundCall.externalId, targetUserId: disabledTargetUser.id }),
75
+ { shouldError: true, onError: (e: any) => e.message === "Target user has incoming calls disabled" }
76
+ )
77
+ // non-admin either can't see the call (Call not found) or is stopped by a later
78
+ // validation (target has incoming calls disabled) — errors on every path
79
+ await async_test(
80
+ "cold_transfer: non-admin cannot transfer an inaccessible call",
81
+ () => sdkNonAdmin.api.phone_calls.cold_transfer({ callSid: inboundCall.externalId, targetUserId: disabledTargetUser.id }),
82
+ handleAnyError
83
+ )
84
+ } finally {
85
+ // deleting the enduser cascades deletion of its phone_calls records
86
+ await Promise.all([
87
+ sdk.api.endusers.deleteOne(enduser.id),
88
+ ...targetUsers.map(u => sdk.api.users.deleteOne(u.id)),
89
+ ]).catch(console.error)
90
+ }
91
+ }
92
+
93
+ // Allow running this test file independently
94
+ if (require.main === module) {
95
+ console.log(`🌐 Using API URL: ${host}`)
96
+ const sdk = new Session({ host })
97
+ const sdkNonAdmin = new Session({ host })
98
+
99
+ const runTests = async () => {
100
+ await setup_tests(sdk, sdkNonAdmin)
101
+ await phone_calls_cold_transfer_tests({ sdk, sdkNonAdmin })
102
+ }
103
+
104
+ runTests()
105
+ .then(() => {
106
+ console.log("✅ Phone calls cold transfer test suite completed successfully")
107
+ process.exit(0)
108
+ })
109
+ .catch((error) => {
110
+ console.error("❌ Phone calls cold transfer test suite failed:", error)
111
+ process.exit(1)
112
+ })
113
+ }
@@ -0,0 +1,174 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ assert,
6
+ async_test,
7
+ handleAnyError,
8
+ log_header,
9
+ } from "@tellescope/testing"
10
+ import { PhoneTreeNode } from "@tellescope/types-models"
11
+ import { setup_tests } from "../setup"
12
+
13
+ const host = process.env.API_URL || 'http://localhost:8080' as const
14
+
15
+ const SPLIT_NODE_ID = 'enduser-condition-split-node'
16
+ const VIP_NODE_ID = 'vip-play-message-node'
17
+ const ELSE_NODE_ID = 'else-play-message-node'
18
+
19
+ const valid_condition_split_nodes: PhoneTreeNode[] = [
20
+ {
21
+ id: SPLIT_NODE_ID,
22
+ action: {
23
+ type: 'Enduser Condition Split',
24
+ info: {
25
+ branches: [
26
+ { name: 'VIP', enduserCondition: { $and: [{ condition: { tags: 'VIP' } }] } },
27
+ { name: 'Everyone Else' }, // no condition — catch-all
28
+ ],
29
+ },
30
+ },
31
+ events: [{ type: 'Start', parentId: SPLIT_NODE_ID, info: {} }],
32
+ flowchartUI: { x: 0, y: 0 },
33
+ },
34
+ {
35
+ id: VIP_NODE_ID,
36
+ action: {
37
+ type: 'Play Message',
38
+ info: { playback: { type: 'Say', info: { script: 'Welcome, VIP caller' } } },
39
+ },
40
+ events: [{ type: 'On Condition Branch', parentId: SPLIT_NODE_ID, info: { branch: 'VIP' } }],
41
+ flowchartUI: { x: -100, y: 100 },
42
+ },
43
+ {
44
+ id: ELSE_NODE_ID,
45
+ action: {
46
+ type: 'Play Message',
47
+ info: { playback: { type: 'Say', info: { script: 'Welcome' } } },
48
+ },
49
+ events: [{ type: 'On Condition Branch', parentId: SPLIT_NODE_ID, info: { branch: 'Everyone Else' } }],
50
+ flowchartUI: { x: 100, y: 100 },
51
+ },
52
+ ]
53
+
54
+ export const phone_tree_enduser_condition_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
55
+ log_header("Phone Tree Enduser Condition Split Tests")
56
+
57
+ const treeIds: string[] = []
58
+ try {
59
+ // valid Enduser Condition Split node with On Condition Branch children saves
60
+ const tree = await sdk.api.phone_trees.createOne({
61
+ number: '+15005550006',
62
+ isActive: false,
63
+ nodes: valid_condition_split_nodes,
64
+ })
65
+ treeIds.push(tree.id)
66
+ assert(!!tree.id, 'failed to create tree with Enduser Condition Split', 'create tree with Enduser Condition Split')
67
+
68
+ const loaded = await sdk.api.phone_trees.getOne(tree.id)
69
+ const loadedSplit = loaded.nodes.find(n => n.action.type === 'Enduser Condition Split')
70
+ assert(
71
+ loadedSplit?.action.type === 'Enduser Condition Split'
72
+ && loadedSplit.action.info.branches.length === 2
73
+ && loadedSplit.action.info.branches[0].name === 'VIP'
74
+ && !!loadedSplit.action.info.branches[0].enduserCondition
75
+ && loadedSplit.action.info.branches[1].enduserCondition === undefined,
76
+ 'branches did not round-trip',
77
+ 'branches (conditions + catch-all) round-trip on read',
78
+ )
79
+
80
+ // regression: existing Conditional Split shape is unaffected
81
+ const conditionalSplitTree = await sdk.api.phone_trees.createOne({
82
+ number: '+15005550006',
83
+ isActive: false,
84
+ nodes: [{
85
+ id: 'conditional-split-node',
86
+ // timezone + weeklyAvailabilities included to match the webapp's default Conditional Split shape
87
+ action: { type: 'Conditional Split', info: { timezone: 'US/Eastern', weeklyAvailabilities: [], hasCareTeam: true } },
88
+ events: [{ type: 'Start', parentId: 'conditional-split-node', info: {} }],
89
+ flowchartUI: { x: 0, y: 0 },
90
+ }],
91
+ })
92
+ treeIds.push(conditionalSplitTree.id)
93
+ assert(!!conditionalSplitTree.id, 'failed to create Conditional Split tree', 'existing Conditional Split still saves (regression)')
94
+
95
+ // invalid: enduserCondition must be an object
96
+ await async_test(
97
+ "non-object enduserCondition is rejected",
98
+ () => sdk.api.phone_trees.createOne({
99
+ number: '+15005550006',
100
+ isActive: false,
101
+ nodes: [{
102
+ id: SPLIT_NODE_ID,
103
+ action: {
104
+ type: 'Enduser Condition Split',
105
+ info: { branches: [{ name: 'Bad', enduserCondition: "not-an-object" as any }] },
106
+ },
107
+ events: [{ type: 'Start', parentId: SPLIT_NODE_ID, info: {} }],
108
+ flowchartUI: { x: 0, y: 0 },
109
+ }],
110
+ }).then(t => { treeIds.push(t.id); return t }), // cleanup if it unexpectedly passes
111
+ handleAnyError,
112
+ )
113
+
114
+ // invalid: branches must include a name
115
+ await async_test(
116
+ "branch without a name is rejected",
117
+ () => sdk.api.phone_trees.createOne({
118
+ number: '+15005550006',
119
+ isActive: false,
120
+ nodes: [{
121
+ id: SPLIT_NODE_ID,
122
+ action: {
123
+ type: 'Enduser Condition Split',
124
+ info: { branches: [{ enduserCondition: {} } as any] },
125
+ },
126
+ events: [{ type: 'Start', parentId: SPLIT_NODE_ID, info: {} }],
127
+ flowchartUI: { x: 0, y: 0 },
128
+ }],
129
+ }).then(t => { treeIds.push(t.id); return t }),
130
+ handleAnyError,
131
+ )
132
+
133
+ // invalid: On Condition Branch events must include a branch value
134
+ await async_test(
135
+ "On Condition Branch event without branch is rejected",
136
+ () => sdk.api.phone_trees.createOne({
137
+ number: '+15005550006',
138
+ isActive: false,
139
+ nodes: valid_condition_split_nodes.map(n => (
140
+ n.id !== VIP_NODE_ID
141
+ ? n
142
+ : { ...n, events: [{ type: 'On Condition Branch', parentId: SPLIT_NODE_ID, info: {} as any }] }
143
+ )),
144
+ }).then(t => { treeIds.push(t.id); return t }),
145
+ handleAnyError,
146
+ )
147
+ } finally {
148
+ for (const id of treeIds) {
149
+ await sdk.api.phone_trees.deleteOne(id).catch(console.error)
150
+ }
151
+ }
152
+ }
153
+
154
+ // Allow running this test file independently
155
+ if (require.main === module) {
156
+ console.log(`Using API URL: ${host}`)
157
+ const sdk = new Session({ host })
158
+ const sdkNonAdmin = new Session({ host })
159
+
160
+ const runTests = async () => {
161
+ await setup_tests(sdk, sdkNonAdmin)
162
+ await phone_tree_enduser_condition_tests({ sdk, sdkNonAdmin })
163
+ }
164
+
165
+ runTests()
166
+ .then(() => {
167
+ console.log("✅ Phone tree enduser condition test suite completed successfully")
168
+ process.exit(0)
169
+ })
170
+ .catch((error) => {
171
+ console.error("❌ Phone tree enduser condition test suite failed:", error)
172
+ process.exit(1)
173
+ })
174
+ }