@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
@@ -0,0 +1,385 @@
1
+ require('source-map-support').install();
2
+
3
+ import crypto from "crypto"
4
+ import { Session } from "../../sdk"
5
+ import {
6
+ async_test,
7
+ log_header,
8
+ assert,
9
+ wait,
10
+ } from "@tellescope/testing"
11
+ import { setup_tests } from "../setup"
12
+
13
+ const host = process.env.API_URL || 'http://localhost:8080' as const
14
+
15
+ const decode_jwt = (token: string): any => {
16
+ try {
17
+ const part = token.split('.')[1]
18
+ const json = Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')
19
+ return JSON.parse(json)
20
+ } catch { return null }
21
+ }
22
+
23
+ // Client-side TOTP mirror (RFC 6238, HMAC-SHA1, 6 digits, 30s period) so the test can
24
+ // compute valid codes from the secret returned by begin_TOTP_configuration
25
+ const TOTP_PERIOD_MS = 30_000
26
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
27
+ const base32_decode = (s: string): Buffer => {
28
+ let bits = 0, value = 0
29
+ const bytes: number[] = []
30
+ for (const char of s.toUpperCase().replace(/=+$/, '')) {
31
+ const index = BASE32_ALPHABET.indexOf(char)
32
+ if (index === -1) throw new Error("Invalid base32 character")
33
+ value = (value << 5) | index
34
+ bits += 5
35
+ if (bits >= 8) {
36
+ bytes.push((value >>> (bits - 8)) & 255)
37
+ bits -= 8
38
+ }
39
+ }
40
+ return Buffer.from(bytes)
41
+ }
42
+ const current_timestep = () => Math.floor(Date.now() / TOTP_PERIOD_MS)
43
+ const compute_totp_code = (secret: string, timestep=current_timestep()): string => {
44
+ const counter = Buffer.alloc(8)
45
+ counter.writeBigUInt64BE(BigInt(timestep))
46
+ const hmac = crypto.createHmac('sha1', base32_decode(secret)).update(counter).digest()
47
+ const offset = hmac[hmac.length - 1] & 0xf
48
+ const binary = (
49
+ ((hmac[offset] & 0x7f) << 24)
50
+ | ((hmac[offset + 1] & 0xff) << 16)
51
+ | ((hmac[offset + 2] & 0xff) << 8)
52
+ | (hmac[offset + 3] & 0xff)
53
+ )
54
+ return (binary % 1_000_000).toString().padStart(6, '0')
55
+ }
56
+ // a syntactically-valid 6-digit code guaranteed not to match the current/adjacent timesteps
57
+ const wrong_totp_code = (secret: string) => {
58
+ const step = current_timestep()
59
+ const valid = new Set([step - 1, step, step + 1].map(s => compute_totp_code(secret, s)))
60
+
61
+ let candidate = (parseInt(compute_totp_code(secret, step)) + 1) % 1_000_000
62
+ while (valid.has(candidate.toString().padStart(6, '0'))) {
63
+ candidate = (candidate + 1) % 1_000_000
64
+ }
65
+ return candidate.toString().padStart(6, '0')
66
+ }
67
+
68
+ export const totp_mfa_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
69
+ log_header("TOTP MFA Tests")
70
+
71
+ const NON_ADMIN_EMAIL = process.env.NON_ADMIN_EMAIL
72
+ const NON_ADMIN_PASSWORD = process.env.NON_ADMIN_PASSWORD
73
+ if (!(NON_ADMIN_EMAIL && NON_ADMIN_PASSWORD)) {
74
+ throw new Error("NON_ADMIN_EMAIL and NON_ADMIN_PASSWORD must be set to run totp_mfa_tests")
75
+ }
76
+
77
+ const nonAdminId = sdkNonAdmin.userInfo.id
78
+ const businessId = sdk.userInfo.businessId
79
+
80
+ // the enrollment secret, captured in section A and used through later sections
81
+ let totpSecret = ''
82
+ // last timestep consumed by a successful server-side verification — a new verification
83
+ // in the same 30s window would be rejected as a replay, so wait for the window to roll over
84
+ let lastVerifiedStep = 0
85
+ const wait_for_fresh_totp_step = async () => {
86
+ while (current_timestep() <= lastVerifiedStep) {
87
+ await wait(undefined, 1000)
88
+ }
89
+ }
90
+
91
+ const set_allowed_mfa_methods = async (methods: string[]) => {
92
+ await sdk.api.users.updateOne(nonAdminId, { allowedMFAMethods: methods } as any, { replaceObjectFields: true })
93
+ await wait(undefined, 250)
94
+ }
95
+ // idempotent clear — skips the PATCH (identical updates to a record are limited to 3/30s)
96
+ const clear_allowed_mfa_methods = async () => {
97
+ const u: any = await sdk.api.users.getOne(nonAdminId)
98
+ if (!(u.allowedMFAMethods ?? []).length) return
99
+ await set_allowed_mfa_methods([])
100
+ }
101
+
102
+ // defensive cleanup of state from a previously-aborted run
103
+ const reset_mfa_state = async () => {
104
+ try { await sdk.api.organizations.updateOne(businessId, { enforceMFA: false } as any) } catch {}
105
+ await wait(undefined, 250)
106
+ try { await clear_allowed_mfa_methods() } catch {}
107
+ try {
108
+ const { authToken, user } = await sdkNonAdmin.api.users.configure_MFA({ disable: true })
109
+ await sdkNonAdmin.handle_new_session({ ...user, authToken } as any)
110
+ } catch {}
111
+ await wait(undefined, 250)
112
+ }
113
+
114
+ await reset_mfa_state()
115
+
116
+ try {
117
+ // ============================================================
118
+ // A. Enrollment
119
+ // ============================================================
120
+ log_header("A. TOTP Enrollment")
121
+
122
+ let otpauthURL = ''
123
+ await async_test(
124
+ 'A1. begin_TOTP_configuration returns otpauthURL + secret',
125
+ () => sdkNonAdmin.api.users.begin_TOTP_configuration({}),
126
+ { shouldError: false, onResult: (r: any) => {
127
+ totpSecret = r.secret
128
+ otpauthURL = r.otpauthURL
129
+ return typeof r.secret === 'string' && r.secret.length >= 16 && /^[A-Z2-7]+$/.test(r.secret)
130
+ }}
131
+ )
132
+ assert(otpauthURL.includes(totpSecret), 'otpauthURL missing secret', 'A2. otpauthURL contains secret')
133
+ assert(otpauthURL.startsWith('otpauth://totp/'), 'unexpected otpauth scheme', 'A3. otpauth scheme')
134
+ assert(otpauthURL.includes('issuer=Tellescope'), 'otpauthURL missing issuer', 'A4. otpauthURL issuer')
135
+
136
+ await async_test(
137
+ 'A5. confirm with wrong code -> 401',
138
+ () => sdkNonAdmin.api.users.confirm_TOTP_configuration({ code: wrong_totp_code(totpSecret) }),
139
+ { shouldError: true, onError: (e: any) => e.statusCode === 401 || /invalid/i.test(e.message || '') }
140
+ )
141
+
142
+ await wait(undefined, 600) // confirm rate limit is 1/500ms
143
+ await async_test(
144
+ 'A6. confirm with correct code -> enabled, 10 recovery codes, requiresMFA false',
145
+ () => sdkNonAdmin.api.users.confirm_TOTP_configuration({ code: compute_totp_code(totpSecret) }),
146
+ { shouldError: false, onResult: (r: any) => {
147
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
148
+ return (
149
+ r.recoveryCodes?.length === 10
150
+ && r.user?.mfa?.authenticator === true
151
+ && decode_jwt(r.authToken)?.requiresMFA === false
152
+ )
153
+ }}
154
+ )
155
+ lastVerifiedStep = current_timestep()
156
+
157
+ await async_test(
158
+ 'A7. user record has mfa.authenticator === true',
159
+ () => sdk.api.users.getOne(nonAdminId),
160
+ { shouldError: false, onResult: (u: any) => u.mfa?.authenticator === true && !u.mfa?.email }
161
+ )
162
+
163
+ await async_test(
164
+ 'A8. begin again while enrolled -> 400',
165
+ () => sdkNonAdmin.api.users.begin_TOTP_configuration({}),
166
+ { shouldError: true, onError: (e: any) => e.statusCode === 400 || /already configured/i.test(e.message || '') }
167
+ )
168
+
169
+ // ============================================================
170
+ // B. Login challenge + replay protection
171
+ // ============================================================
172
+ log_header("B. TOTP Challenge + Replay")
173
+
174
+ const challengeSession = new Session({ host })
175
+ await challengeSession.authenticate(NON_ADMIN_EMAIL, NON_ADMIN_PASSWORD)
176
+ assert(
177
+ !!challengeSession.userInfo.requiresMFA || decode_jwt((challengeSession as any).authToken)?.requiresMFA === true,
178
+ 'expected requiresMFA session after password login with TOTP enabled',
179
+ 'B1. password login yields requiresMFA session'
180
+ )
181
+
182
+ await wait_for_fresh_totp_step()
183
+ const challengeCode = compute_totp_code(totpSecret)
184
+ await async_test(
185
+ 'B2. submit_MFA_challenge with TOTP code -> full session',
186
+ () => challengeSession.api.users.submit_MFA_challenge({ code: challengeCode }),
187
+ { shouldError: false, onResult: (r: any) => decode_jwt(r.authToken)?.requiresMFA === false }
188
+ )
189
+ lastVerifiedStep = current_timestep()
190
+
191
+ // replay: same code from a fresh requiresMFA session must be rejected
192
+ const replaySession = new Session({ host })
193
+ await replaySession.authenticate(NON_ADMIN_EMAIL, NON_ADMIN_PASSWORD)
194
+ await wait(undefined, 600) // submit rate limit is 1/500ms
195
+ await async_test(
196
+ 'B3. replaying the same TOTP code -> 401',
197
+ () => replaySession.api.users.submit_MFA_challenge({ code: challengeCode }),
198
+ { shouldError: true, onError: (e: any) => e.statusCode === 401 || /invalid/i.test(e.message || '') }
199
+ )
200
+
201
+ // ============================================================
202
+ // C. Recovery-code interplay with a second method
203
+ // ============================================================
204
+ log_header("C. Recovery-code interplay")
205
+
206
+ await async_test(
207
+ 'C1. enabling email MFA when TOTP already configured -> recoveryCodes []',
208
+ () => sdkNonAdmin.api.users.configure_MFA({}),
209
+ { shouldError: false, onResult: (r: any) => {
210
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
211
+ return r.recoveryCodes?.length === 0 && r.user?.mfa?.email === true && r.user?.mfa?.authenticator === true
212
+ }}
213
+ )
214
+
215
+ // ============================================================
216
+ // D. Method-scoped disable + enforceMFA last-method rule
217
+ // ============================================================
218
+ log_header("D. Method-scoped disable")
219
+
220
+ await sdk.api.organizations.updateOne(businessId, { enforceMFA: true } as any)
221
+ await wait(undefined, 250)
222
+
223
+ await async_test(
224
+ 'D1. disable email (other method remains) succeeds under enforceMFA',
225
+ () => sdkNonAdmin.api.users.configure_MFA({ disable: true, method: 'email' }),
226
+ { shouldError: false, onResult: (r: any) => {
227
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
228
+ return !r.user?.mfa?.email && r.user?.mfa?.authenticator === true
229
+ }}
230
+ )
231
+
232
+ await async_test(
233
+ 'D2. disabling last method under enforceMFA -> 400',
234
+ () => sdkNonAdmin.api.users.configure_MFA({ disable: true, method: 'authenticator' }),
235
+ { shouldError: true, onError: (e: any) => e.statusCode === 400 || /at least one/i.test(e.message || '') }
236
+ )
237
+
238
+ await sdk.api.organizations.updateOne(businessId, { enforceMFA: false } as any)
239
+ await wait(undefined, 250)
240
+
241
+ await async_test(
242
+ 'D3. disable authenticator (enforceMFA off) succeeds',
243
+ () => sdkNonAdmin.api.users.configure_MFA({ disable: true, method: 'authenticator' }),
244
+ { shouldError: false, onResult: (r: any) => {
245
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
246
+ return !r.user?.mfa?.authenticator && !r.user?.mfa?.email
247
+ }}
248
+ )
249
+
250
+ await async_test(
251
+ 'D4. user record mfa cleared',
252
+ () => sdk.api.users.getOne(nonAdminId),
253
+ { shouldError: false, onResult: (u: any) => !u.mfa?.authenticator && !u.mfa?.email }
254
+ )
255
+
256
+ // TOTP secret deleted with the method: a still-valid-looking code must no longer verify
257
+ await wait_for_fresh_totp_step()
258
+ await wait(undefined, 600) // submit rate limit is 1/500ms
259
+ await async_test(
260
+ 'D5. TOTP submit after disable -> 401 (secret deleted)',
261
+ () => sdkNonAdmin.api.users.submit_MFA_challenge({ code: compute_totp_code(totpSecret) }),
262
+ { shouldError: true, onError: (e: any) => e.statusCode === 401 || /invalid/i.test(e.message || '') }
263
+ )
264
+
265
+ // ============================================================
266
+ // E. allowedMFAMethods restriction gating + grace
267
+ // ============================================================
268
+ log_header("E. allowedMFAMethods gating")
269
+
270
+ await set_allowed_mfa_methods(['authenticator'])
271
+
272
+ await async_test(
273
+ 'E1. restricted to authenticator: enabling email MFA -> 403',
274
+ () => sdkNonAdmin.api.users.configure_MFA({}),
275
+ { shouldError: true, onError: (e: any) => e.statusCode === 403 || /not permitted/i.test(e.message || '') }
276
+ )
277
+
278
+ await async_test(
279
+ 'E2. restricted to authenticator + email unconfigured: email challenge -> 403',
280
+ () => sdkNonAdmin.api.users.generate_MFA_challenge({ method: 'email' }),
281
+ { shouldError: true, onError: (e: any) => e.statusCode === 403 || /not permitted/i.test(e.message || '') }
282
+ )
283
+
284
+ await set_allowed_mfa_methods(['email'])
285
+
286
+ await async_test(
287
+ 'E3. restricted to email: begin_TOTP_configuration -> 403',
288
+ () => sdkNonAdmin.api.users.begin_TOTP_configuration({}),
289
+ { shouldError: true, onError: (e: any) => e.statusCode === 403 || /not permitted/i.test(e.message || '') }
290
+ )
291
+
292
+ await async_test(
293
+ 'E4. restricted to email: enabling email MFA succeeds (first method -> 10 recovery codes)',
294
+ () => sdkNonAdmin.api.users.configure_MFA({}),
295
+ { shouldError: false, onResult: (r: any) => {
296
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
297
+ return r.recoveryCodes?.length === 10 && r.user?.mfa?.email === true
298
+ }}
299
+ )
300
+
301
+ // grace: email is configured, then the user is restricted to authenticator-only —
302
+ // the already-configured email method must keep working (no lockout)
303
+ await set_allowed_mfa_methods(['authenticator'])
304
+
305
+ await async_test(
306
+ 'E5. grace: configured email challenge still works after restriction',
307
+ () => sdkNonAdmin.api.users.generate_MFA_challenge({ method: 'email' }),
308
+ { shouldError: false, onResult: () => true }
309
+ )
310
+
311
+ // cleanup E: clear restriction + disable email
312
+ await clear_allowed_mfa_methods()
313
+ await async_test(
314
+ 'E6. cleanup: disable email',
315
+ () => sdkNonAdmin.api.users.configure_MFA({ disable: true, method: 'email' }),
316
+ { shouldError: false, onResult: (r: any) => {
317
+ if (r.authToken) sdkNonAdmin.handle_new_session({ ...r.user, authToken: r.authToken } as any)
318
+ return true
319
+ }}
320
+ )
321
+
322
+ // ============================================================
323
+ // F. allowedMFAMethods is admin-only
324
+ // ============================================================
325
+ log_header("F. allowedMFAMethods admin gate")
326
+
327
+ await async_test(
328
+ 'F1. non-admin cannot update allowedMFAMethods',
329
+ () => sdkNonAdmin.api.users.updateOne(nonAdminId, { allowedMFAMethods: ['email'] } as any, { replaceObjectFields: true }),
330
+ { shouldError: true, onError: (e: any) => /admin/i.test(e.message || '') || e.statusCode === 400 || e.statusCode === 403 }
331
+ )
332
+
333
+ await async_test(
334
+ 'F2. admin can update allowedMFAMethods',
335
+ () => sdk.api.users.updateOne(nonAdminId, { allowedMFAMethods: ['email'] } as any, { replaceObjectFields: true }),
336
+ { shouldError: false, onResult: () => true }
337
+ )
338
+ await wait(undefined, 250)
339
+ await async_test(
340
+ 'F3. allowedMFAMethods persisted',
341
+ () => sdk.api.users.getOne(nonAdminId),
342
+ { shouldError: false, onResult: (u: any) => JSON.stringify(u.allowedMFAMethods) === JSON.stringify(['email']) }
343
+ )
344
+ await clear_allowed_mfa_methods()
345
+
346
+ // ============================================================
347
+ // G. Brute-force rate limit (runs last: exhausts the 10/10min submit bucket)
348
+ // ============================================================
349
+ log_header("G. Brute-force rate limit")
350
+
351
+ let saw429 = false
352
+ for (let i = 0; i < 12 && !saw429; i++) {
353
+ await wait(undefined, 600) // stay under the 1/500ms bucket so only the 10/10min bucket trips
354
+ try {
355
+ await sdkNonAdmin.api.users.submit_MFA_challenge({ code: '123456' })
356
+ } catch (e: any) {
357
+ if (e.statusCode === 429 || /rate limit/i.test(e.message || '')) saw429 = true
358
+ }
359
+ }
360
+ assert(saw429, 'expected 429 from repeated bad MFA submissions', 'G1. repeated bad codes hit 10/10min rate limit')
361
+ } finally {
362
+ await reset_mfa_state()
363
+ }
364
+ }
365
+
366
+ // Allow running this test file independently
367
+ if (require.main === module) {
368
+ const sdk = new Session({ host })
369
+ const sdkNonAdmin = new Session({ host })
370
+
371
+ const runTests = async () => {
372
+ await setup_tests(sdk, sdkNonAdmin)
373
+ await totp_mfa_tests({ sdk, sdkNonAdmin })
374
+ }
375
+
376
+ runTests()
377
+ .then(() => {
378
+ console.log("✅ TOTP MFA test suite completed successfully")
379
+ process.exit(0)
380
+ })
381
+ .catch((error) => {
382
+ console.error("❌ TOTP MFA test suite failed:", error)
383
+ process.exit(1)
384
+ })
385
+ }
@@ -81,6 +81,7 @@ import {
81
81
 
82
82
  import fs from "fs"
83
83
  import { load_inbox_data_tests } from "./api_tests/load_inbox_data.test";
84
+ import { phone_calls_cold_transfer_tests } from "./api_tests/phone_calls_cold_transfer.test";
84
85
  import { enduser_login_tests } from "./api_tests/enduser_login.test";
85
86
  import { enduser_login_rate_limits_tests } from "./api_tests/enduser_login_rate_limits.test";
86
87
  import { eom_procedure_codes_tests } from "./api_tests/eom_procedure_codes.test";
@@ -127,18 +128,21 @@ import { conditional_logic_medication_unit_tests } from "./unit_tests/conditiona
127
128
  import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
128
129
  import { organization_settings_duplicates_tests } from "./api_tests/organization_settings_duplicates.test";
129
130
  import { calendar_events_bulk_update_tests } from "./api_tests/calendar_events_bulk_update.test";
131
+ import { calendar_event_references_update_tests } from "./api_tests/calendar_event_references_update.test";
130
132
  import { openloop_webhooks_tests } from "./api_tests/openloop_webhooks.test";
131
133
  import { beluga_pharmacy_mappings_tests } from "./api_tests/beluga_pharmacy_mappings.test";
132
134
  import { mdi_case_offerings_tests } from "./api_tests/mdi_case_offerings.test";
133
135
  import { beluga_manual_sync_tests } from "./api_tests/beluga_manual_sync.test";
134
136
  import { mdi_webhooks_tests } from "./api_tests/mdi_webhooks.test";
135
137
  import { account_switcher_tests } from "./api_tests/account_switcher.test";
138
+ import { totp_mfa_tests } from "./api_tests/totp_mfa.test";
136
139
  import { set_fields_order_templates_tests } from "./api_tests/set_fields_order_templates.test";
137
140
  import { date_string_validation_tests } from "./api_tests/date_string_validation.test";
138
141
  import { enduser_session_invalidation_tests } from "./api_tests/enduser_session_invalidation.test";
139
142
  import { enduser_cross_access_isolation_tests } from "./api_tests/enduser_cross_access_isolation.test";
140
143
  import { calendar_event_webhook_template_tests } from "./api_tests/calendar_event_webhook_template.test";
141
144
  import { webhook_error_handling_tests } from "./api_tests/webhook_error_handling.test";
145
+ import { phone_tree_enduser_condition_tests } from "./api_tests/phone_tree_enduser_condition.test";
142
146
 
143
147
  const UniquenessViolationMessage = 'Uniqueness Violation'
144
148
 
@@ -15072,6 +15076,7 @@ const ip_address_form_tests = async () => {
15072
15076
  await sanitize_user_html_xss_tests()
15073
15077
  await prototype_pollution_tests()
15074
15078
  await account_switcher_tests({ sdk, sdkNonAdmin })
15079
+ await totp_mfa_tests({ sdk, sdkNonAdmin })
15075
15080
  await enduser_login_tests({ sdk, sdkNonAdmin })
15076
15081
  await outbound_chat_sent_trigger_tests({ sdk })
15077
15082
  await enduser_cross_access_isolation_tests({ sdk, sdkNonAdmin })
@@ -15085,6 +15090,7 @@ const ip_address_form_tests = async () => {
15085
15090
  await form_submitted_trigger_tests({ sdk, sdkNonAdmin })
15086
15091
  await dont_sync_to_elation_form_submission_tests({ sdk, sdkNonAdmin })
15087
15092
  await date_string_validation_tests({ sdk, sdkNonAdmin })
15093
+ await phone_tree_enduser_condition_tests({ sdk, sdkNonAdmin })
15088
15094
  await openloop_webhooks_tests({ sdk, sdkNonAdmin })
15089
15095
  await mdi_webhooks_tests({ sdk, sdkNonAdmin })
15090
15096
  await integrations_redacted_tests({ sdk, sdkNonAdmin })
@@ -15129,6 +15135,7 @@ const ip_address_form_tests = async () => {
15129
15135
  await inbox_threads_building_tests()
15130
15136
  await inbox_threads_loading_tests()
15131
15137
  await load_inbox_data_tests({ sdk, sdkNonAdmin })
15138
+ await phone_calls_cold_transfer_tests({ sdk, sdkNonAdmin })
15132
15139
  await enduser_observations_acknowledge_tests({ sdk, sdkNonAdmin })
15133
15140
  await nutrition_tracking_tests({ sdk, sdkNonAdmin })
15134
15141
  await translations_tests({ sdk, sdkNonAdmin })
@@ -15140,6 +15147,7 @@ const ip_address_form_tests = async () => {
15140
15147
  await ip_address_form_tests()
15141
15148
  await bulk_update_tests()
15142
15149
  await calendar_events_bulk_update_tests({ sdk })
15150
+ await calendar_event_references_update_tests({ sdk, sdkNonAdmin })
15143
15151
  await cancel_upcoming_appointments_journey_action_test()
15144
15152
  await multi_tenant_tests() // should come right after setup tests
15145
15153
  await sync_tests_with_access_tags() // should come directly after setup to avoid extra sync values
Binary file