@tellescope/sdk 1.255.18 → 1.256.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 (165) hide show
  1. package/lib/cjs/sdk.d.ts +7 -2
  2. package/lib/cjs/sdk.d.ts.map +1 -1
  3. package/lib/cjs/sdk.js +1 -0
  4. package/lib/cjs/sdk.js.map +1 -1
  5. package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.d.ts.map +1 -1
  6. package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js +18 -8
  7. package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
  8. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
  9. package/lib/cjs/tests/api_tests/custom_dashboards.test.js +99 -8
  10. package/lib/cjs/tests/api_tests/custom_dashboards.test.js.map +1 -1
  11. package/lib/cjs/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
  12. package/lib/cjs/tests/api_tests/default_summary_journey.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
  14. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
  15. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js +278 -0
  16. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
  17. package/lib/cjs/tests/api_tests/healthie_multi_integration.test.d.ts.map +1 -1
  18. package/lib/cjs/tests/api_tests/healthie_multi_integration.test.js +203 -72
  19. package/lib/cjs/tests/api_tests/healthie_multi_integration.test.js.map +1 -1
  20. package/lib/cjs/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
  21. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +212 -33
  22. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  23. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
  24. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
  25. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
  26. package/lib/cjs/tests/api_tests/phone_calls_conference_hold.test.d.ts +6 -0
  27. package/lib/cjs/tests/api_tests/phone_calls_conference_hold.test.d.ts.map +1 -0
  28. package/lib/cjs/tests/api_tests/phone_calls_conference_hold.test.js +102 -0
  29. package/lib/cjs/tests/api_tests/phone_calls_conference_hold.test.js.map +1 -0
  30. package/lib/cjs/tests/api_tests/scoped_api_keys.test.d.ts.map +1 -1
  31. package/lib/cjs/tests/api_tests/scoped_api_keys.test.js +84 -81
  32. package/lib/cjs/tests/api_tests/scoped_api_keys.test.js.map +1 -1
  33. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
  34. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  35. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +242 -0
  36. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
  37. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
  38. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
  39. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +443 -0
  40. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
  41. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
  42. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  43. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +231 -0
  44. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
  45. package/lib/cjs/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.d.ts.map +1 -1
  46. package/lib/cjs/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.js +121 -78
  47. package/lib/cjs/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.js.map +1 -1
  48. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts +6 -0
  49. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -0
  50. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +1003 -0
  51. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -0
  52. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.d.ts +17 -0
  53. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.d.ts.map +1 -0
  54. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js +215 -0
  55. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js.map +1 -0
  56. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts +6 -0
  57. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -0
  58. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +1200 -0
  59. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -0
  60. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts +6 -0
  61. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -0
  62. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +2330 -0
  63. package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -0
  64. package/lib/cjs/tests/api_tests/tickets_bulk_assign_care_team.test.d.ts +6 -0
  65. package/lib/cjs/tests/api_tests/tickets_bulk_assign_care_team.test.d.ts.map +1 -0
  66. package/lib/cjs/tests/api_tests/tickets_bulk_assign_care_team.test.js +204 -0
  67. package/lib/cjs/tests/api_tests/tickets_bulk_assign_care_team.test.js.map +1 -0
  68. package/lib/cjs/tests/tests.d.ts.map +1 -1
  69. package/lib/cjs/tests/tests.js +489 -255
  70. package/lib/cjs/tests/tests.js.map +1 -1
  71. package/lib/esm/enduser.d.ts +0 -1
  72. package/lib/esm/enduser.d.ts.map +1 -1
  73. package/lib/esm/sdk.d.ts +9 -5
  74. package/lib/esm/sdk.d.ts.map +1 -1
  75. package/lib/esm/sdk.js +1 -0
  76. package/lib/esm/sdk.js.map +1 -1
  77. package/lib/esm/session.d.ts +0 -1
  78. package/lib/esm/session.d.ts.map +1 -1
  79. package/lib/esm/tests/api_tests/auto_merge_form_submission.test.d.ts.map +1 -1
  80. package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js +18 -8
  81. package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
  82. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
  83. package/lib/esm/tests/api_tests/custom_dashboards.test.js +99 -8
  84. package/lib/esm/tests/api_tests/custom_dashboards.test.js.map +1 -1
  85. package/lib/esm/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
  86. package/lib/esm/tests/api_tests/default_summary_journey.test.js.map +1 -0
  87. package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
  88. package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
  89. package/lib/esm/tests/api_tests/file_download_unicode_names.test.js +248 -0
  90. package/lib/esm/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
  91. package/lib/esm/tests/api_tests/healthie_multi_integration.test.d.ts.map +1 -1
  92. package/lib/esm/tests/api_tests/healthie_multi_integration.test.js +205 -74
  93. package/lib/esm/tests/api_tests/healthie_multi_integration.test.js.map +1 -1
  94. package/lib/esm/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
  95. package/lib/esm/tests/api_tests/openloop_webhooks.test.js +213 -34
  96. package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  97. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
  98. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
  99. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
  100. package/lib/esm/tests/api_tests/phone_calls_conference_hold.test.d.ts +6 -0
  101. package/lib/esm/tests/api_tests/phone_calls_conference_hold.test.d.ts.map +1 -0
  102. package/lib/esm/tests/api_tests/phone_calls_conference_hold.test.js +98 -0
  103. package/lib/esm/tests/api_tests/phone_calls_conference_hold.test.js.map +1 -0
  104. package/lib/esm/tests/api_tests/scoped_api_keys.test.d.ts.map +1 -1
  105. package/lib/esm/tests/api_tests/scoped_api_keys.test.js +84 -81
  106. package/lib/esm/tests/api_tests/scoped_api_keys.test.js.map +1 -1
  107. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
  108. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  109. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +238 -0
  110. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
  111. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
  112. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
  113. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +436 -0
  114. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
  115. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
  116. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  117. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +224 -0
  118. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
  119. package/lib/esm/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.d.ts.map +1 -1
  120. package/lib/esm/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.js +121 -78
  121. package/lib/esm/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.js.map +1 -1
  122. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts +6 -0
  123. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -0
  124. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +996 -0
  125. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -0
  126. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.d.ts +17 -0
  127. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.d.ts.map +1 -0
  128. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js +211 -0
  129. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js.map +1 -0
  130. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts +6 -0
  131. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -0
  132. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +1193 -0
  133. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -0
  134. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts +6 -0
  135. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -0
  136. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +2323 -0
  137. package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -0
  138. package/lib/esm/tests/api_tests/tickets_bulk_assign_care_team.test.d.ts +6 -0
  139. package/lib/esm/tests/api_tests/tickets_bulk_assign_care_team.test.d.ts.map +1 -0
  140. package/lib/esm/tests/api_tests/tickets_bulk_assign_care_team.test.js +200 -0
  141. package/lib/esm/tests/api_tests/tickets_bulk_assign_care_team.test.js.map +1 -0
  142. package/lib/esm/tests/tests.d.ts.map +1 -1
  143. package/lib/esm/tests/tests.js +489 -255
  144. package/lib/esm/tests/tests.js.map +1 -1
  145. package/lib/tsconfig.tsbuildinfo +1 -1
  146. package/package.json +10 -10
  147. package/src/sdk.ts +4 -0
  148. package/src/tests/api_tests/auto_merge_form_submission.test.ts +13 -4
  149. package/src/tests/api_tests/custom_dashboards.test.ts +92 -0
  150. package/src/tests/api_tests/file_download_unicode_names.test.ts +223 -0
  151. package/src/tests/api_tests/healthie_multi_integration.test.ts +103 -5
  152. package/src/tests/api_tests/openloop_webhooks.test.ts +138 -0
  153. package/src/tests/api_tests/phone_calls_cold_transfer.test.ts +54 -0
  154. package/src/tests/api_tests/phone_calls_conference_hold.test.ts +62 -0
  155. package/src/tests/api_tests/scoped_api_keys.test.ts +5 -0
  156. package/src/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.ts +215 -0
  157. package/src/tests/api_tests/security/F-0106-F-0110-enduser-write-restrictions.test.ts +38 -4
  158. package/src/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.ts +721 -0
  159. package/src/tests/api_tests/security/enduser-ai-summary-trust-gate.test.ts +125 -0
  160. package/src/tests/api_tests/security/formsort_webhook_auth_field_lock.test.ts +753 -0
  161. package/src/tests/api_tests/security/public_endpoint_auth_field_lock.test.ts +1707 -0
  162. package/src/tests/api_tests/tickets_bulk_assign_care_team.test.ts +132 -0
  163. package/src/tests/tests.ts +232 -2
  164. package/test_generated.pdf +0 -0
  165. package/.env +0 -13
@@ -0,0 +1,132 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ log_header,
7
+ } from "@tellescope/testing"
8
+ import { setup_tests } from "../setup"
9
+
10
+ const host = process.env.API_URL || 'http://localhost:8080' as const
11
+
12
+ // Main test function for tickets bulk_assign addToCareTeam flag
13
+ export const tickets_bulk_assign_care_team_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
14
+ log_header("Tickets Bulk Assign (addToCareTeam) Tests")
15
+
16
+ // Throwaway user to act as the new ticket owner
17
+ const owner = await sdk.api.users.createOne({
18
+ email: `test-bulk-assign-owner-${Date.now()}@test.com`,
19
+ fname: 'BulkAssign',
20
+ lname: 'Owner',
21
+ verifiedEmail: true,
22
+ })
23
+
24
+ const enduser1 = await sdk.api.endusers.createOne({ fname: 'BulkAssign', lname: 'PatientOne' })
25
+ const enduser2 = await sdk.api.endusers.createOne({ fname: 'BulkAssign', lname: 'PatientTwo' })
26
+
27
+ // Tickets are created without an owner so the on-create care team side effect doesn't run
28
+ const ticket1 = await sdk.api.tickets.createOne({ title: 'Bulk Assign Test 1', enduserId: enduser1.id })
29
+ const ticket2 = await sdk.api.tickets.createOne({ title: 'Bulk Assign Test 2', enduserId: enduser1.id })
30
+ const ticket3 = await sdk.api.tickets.createOne({ title: 'Bulk Assign Test 3', enduserId: enduser2.id, skipCareTeamAssignment: true })
31
+ const ticketNoEnduser = await sdk.api.tickets.createOne({ title: 'Bulk Assign Test 4' })
32
+
33
+ try {
34
+ // Default behavior (flag omitted): owner set, care team untouched
35
+ await async_test(
36
+ "bulk_assign: assigns owner without flag",
37
+ () => sdk.api.tickets.bulk_assign({ ids: [ticket1.id], userId: owner.id }),
38
+ { onResult: () => true }
39
+ )
40
+ await async_test(
41
+ "bulk_assign: owner set on ticket (no flag)",
42
+ () => sdk.api.tickets.getOne(ticket1.id),
43
+ { onResult: t => t.owner === owner.id }
44
+ )
45
+ await async_test(
46
+ "bulk_assign: care team unchanged without flag",
47
+ () => sdk.api.endusers.getOne(enduser1.id),
48
+ { onResult: e => !e.assignedTo?.includes(owner.id) }
49
+ )
50
+
51
+ // addToCareTeam: false behaves like omitted
52
+ await async_test(
53
+ "bulk_assign: addToCareTeam false leaves care team unchanged",
54
+ async () => {
55
+ await sdk.api.tickets.bulk_assign({ ids: [ticket1.id], userId: owner.id, addToCareTeam: false })
56
+ return sdk.api.endusers.getOne(enduser1.id)
57
+ },
58
+ { onResult: e => !e.assignedTo?.includes(owner.id) }
59
+ )
60
+
61
+ // addToCareTeam: true adds owner to each ticket's enduser care team,
62
+ // including tickets flagged skipCareTeamAssignment (explicit flag wins),
63
+ // and tolerates tickets with no enduser
64
+ await async_test(
65
+ "bulk_assign: addToCareTeam true assigns owner and updates care teams",
66
+ () => sdk.api.tickets.bulk_assign({
67
+ ids: [ticket1.id, ticket2.id, ticket3.id, ticketNoEnduser.id],
68
+ userId: owner.id,
69
+ addToCareTeam: true,
70
+ }),
71
+ { onResult: () => true }
72
+ )
73
+ await async_test(
74
+ "bulk_assign: owner added to care team (enduser1)",
75
+ () => sdk.api.endusers.getOne(enduser1.id),
76
+ { onResult: e => !!e.assignedTo?.includes(owner.id) }
77
+ )
78
+ await async_test(
79
+ "bulk_assign: owner added to care team despite skipCareTeamAssignment (enduser2)",
80
+ () => sdk.api.endusers.getOne(enduser2.id),
81
+ { onResult: e => !!e.assignedTo?.includes(owner.id) }
82
+ )
83
+ await async_test(
84
+ "bulk_assign: ticket without enduser still assigned",
85
+ () => sdk.api.tickets.getOne(ticketNoEnduser.id),
86
+ { onResult: t => t.owner === owner.id }
87
+ )
88
+
89
+ // Idempotent when owner already on care team
90
+ await async_test(
91
+ "bulk_assign: addToCareTeam is idempotent",
92
+ async () => {
93
+ await sdk.api.tickets.bulk_assign({ ids: [ticket1.id], userId: owner.id, addToCareTeam: true })
94
+ return sdk.api.endusers.getOne(enduser1.id)
95
+ },
96
+ { onResult: e => e.assignedTo?.filter(id => id === owner.id).length === 1 }
97
+ )
98
+ } finally {
99
+ // Cleanup
100
+ await Promise.all([
101
+ sdk.api.tickets.deleteOne(ticket1.id).catch(console.error),
102
+ sdk.api.tickets.deleteOne(ticket2.id).catch(console.error),
103
+ sdk.api.tickets.deleteOne(ticket3.id).catch(console.error),
104
+ sdk.api.tickets.deleteOne(ticketNoEnduser.id).catch(console.error),
105
+ sdk.api.endusers.deleteOne(enduser1.id).catch(console.error),
106
+ sdk.api.endusers.deleteOne(enduser2.id).catch(console.error),
107
+ sdk.api.users.deleteOne(owner.id).catch(console.error),
108
+ ])
109
+ }
110
+ }
111
+
112
+ // Allow running this test file independently
113
+ if (require.main === module) {
114
+ console.log(`🌐 Using API URL: ${host}`)
115
+ const sdk = new Session({ host })
116
+ const sdkNonAdmin = new Session({ host })
117
+
118
+ const runTests = async () => {
119
+ await setup_tests(sdk, sdkNonAdmin)
120
+ await tickets_bulk_assign_care_team_tests({ sdk, sdkNonAdmin })
121
+ }
122
+
123
+ runTests()
124
+ .then(() => {
125
+ console.log("✅ Tickets bulk assign care team test suite completed successfully")
126
+ process.exit(0)
127
+ })
128
+ .catch((error) => {
129
+ console.error("❌ Tickets bulk assign care team test suite failed:", error)
130
+ process.exit(1)
131
+ })
132
+ }
@@ -17,6 +17,7 @@ import {
17
17
  CompoundFilter,
18
18
  CreateTicketActionInfo,
19
19
  FormResponseValue,
20
+ JourneyContext,
20
21
  ModelName,
21
22
  RoundRobinAssignmentInfo,
22
23
  User,
@@ -41,6 +42,7 @@ import { user_portal_settings_tests } from "./api_tests/user_portal_settings.tes
41
42
  import { integrations_redacted_tests } from "./api_tests/integrations_redacted.test"
42
43
  import { healthie_multi_integration_tests } from "./api_tests/healthie_multi_integration.test"
43
44
  import { get_some_projection_tests } from "./api_tests/get_some_projection.test"
45
+ import { file_download_unicode_names_tests } from "./api_tests/file_download_unicode_names.test"
44
46
  import { mdb_sort_tests } from "./api_tests/mdb_sort.test"
45
47
  import { create_user_notifications_trigger_tests } from "./api_tests/create_user_notifications_trigger.test"
46
48
  import { inbox_thread_assignment_updates_tests } from "./api_tests/inbox_thread_assignment_updates.test"
@@ -82,6 +84,7 @@ import {
82
84
  import fs from "fs"
83
85
  import { load_inbox_data_tests } from "./api_tests/load_inbox_data.test";
84
86
  import { phone_calls_cold_transfer_tests } from "./api_tests/phone_calls_cold_transfer.test";
87
+ import { phone_calls_conference_hold_tests } from "./api_tests/phone_calls_conference_hold.test";
85
88
  import { enduser_login_tests } from "./api_tests/enduser_login.test";
86
89
  import { enduser_login_rate_limits_tests } from "./api_tests/enduser_login_rate_limits.test";
87
90
  import { eom_procedure_codes_tests } from "./api_tests/eom_procedure_codes.test";
@@ -109,12 +112,18 @@ import { invite_user_enumeration_tests } from "./api_tests/security/F-0007-invit
109
112
  import { register_business_id_injection_tests } from "./api_tests/security/register_business_id_injection.test";
110
113
  import { handle_incoming_communication_cross_tenant_tests } from "./api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test";
111
114
  import { sanitize_user_html_xss_tests } from "./api_tests/security/F-0013-sanitize-user-html.test";
115
+ import { sanitize_user_html_with_iframes_tests } from "./api_tests/security/F-0013-sanitize-user-html-with-iframes.test";
112
116
  import { prototype_pollution_tests } from "./api_tests/security/F-0016-prototype-pollution.test";
113
117
  import { enduser_cross_create_tests } from "./api_tests/security/F-0116-F-0119-enduser-cross-create.test";
114
118
  import { globalunique_leak_tests } from "./api_tests/security/F-0145-globalunique-leak.test";
115
119
  import { load_inbox_redaction_tests } from "./api_tests/security/F-0151-load-inbox-redaction.test";
116
120
  import { webhook_timeout_tests } from "./api_tests/security/F-0155-webhook-timeout.test";
121
+ import { enduser_ai_summary_trust_gate_tests } from "./api_tests/security/enduser-ai-summary-trust-gate.test";
122
+ import { allowed_paths_confinement_tests } from "./api_tests/security/F-0166-allowed-paths-wildcard-bypass.test";
123
+ import { public_endpoint_auth_field_lock_tests } from "./api_tests/security/public_endpoint_auth_field_lock.test";
124
+ import { formsort_webhook_auth_field_lock_tests } from "./api_tests/security/formsort_webhook_auth_field_lock.test";
117
125
  import { bulk_assignment_tests } from "./api_tests/bulk_assignment.test";
126
+ import { tickets_bulk_assign_care_team_tests } from "./api_tests/tickets_bulk_assign_care_team.test";
118
127
  import { managed_content_enduser_access_tests } from "./api_tests/managed_content_enduser_access.test";
119
128
  import { managed_content_file_access_tests } from "./api_tests/managed_content_file_access.test";
120
129
  import { auto_merge_form_submission_tests } from "./api_tests/auto_merge_form_submission.test";
@@ -7945,9 +7954,49 @@ const merge_enduser_tests = async () => {
7945
7954
  { onResult: e => e.attendees.length === 1 && !!e.attendees?.find(a => a.id === destination.id) }
7946
7955
  )
7947
7956
 
7957
+ // merging two endusers that both lack insurance must leave the fields absent, NOT null.
7958
+ // a stored null breaks every later partial update: those get flattened to dotted paths
7959
+ // ('insurance.memberId'), and MongoDB rejects a dotted $set under a null parent
7960
+ const [noInsuranceSource, noInsuranceDestination] = (await sdk.api.endusers.createSome([
7961
+ { email: 'noins-source@tellescope.com', fname: 'no', lname: 'insurance' },
7962
+ { email: 'noins-destination@tellescope.com' },
7963
+ ])).created
7964
+
7965
+ await sdk.api.endusers.merge({
7966
+ sourceEnduserId: noInsuranceSource.id,
7967
+ destinationEnduserId: noInsuranceDestination.id,
7968
+ })
7969
+
7970
+ await async_test(
7971
+ "Merged endusers have no null insurance",
7972
+ () => sdk.api.endusers.getOne(noInsuranceDestination.id),
7973
+ { onResult: e => e.insurance === undefined && e.insuranceSecondary === undefined }
7974
+ )
7975
+
7976
+ await async_test(
7977
+ "Partial insurance update applies after merge",
7978
+ async () => {
7979
+ await sdk.api.endusers.updateOne(noInsuranceDestination.id, {
7980
+ insurance: { memberId: 'M1', payerName: 'Test Payer' },
7981
+ }) // no replaceObjectFields, matching the standard update from the UI
7982
+ return await sdk.api.endusers.getOne(noInsuranceDestination.id)
7983
+ },
7984
+ { onResult: e => e.insurance?.memberId === 'M1' && e.insurance?.payerName === 'Test Payer' }
7985
+ )
7986
+
7987
+ await async_test(
7988
+ "Second partial insurance update merges",
7989
+ async () => {
7990
+ await sdk.api.endusers.updateOne(noInsuranceDestination.id, { insurance: { groupNumber: 'G1' } })
7991
+ return await sdk.api.endusers.getOne(noInsuranceDestination.id)
7992
+ },
7993
+ { onResult: e => e.insurance?.groupNumber === 'G1' && e.insurance?.memberId === 'M1' }
7994
+ )
7995
+
7948
7996
  await Promise.all([
7949
7997
  sdk.api.endusers.deleteOne(destination.id),
7950
7998
  sdk.api.endusers.deleteOne(otherEnduser.id),
7999
+ sdk.api.endusers.deleteOne(noInsuranceDestination.id),
7951
8000
  sdk.api.calendar_events.deleteOne(event.id),
7952
8001
  sdk.api.calendar_events.deleteOne(eventToMove.id),
7953
8002
  ])
@@ -8581,6 +8630,8 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8581
8630
  enduser,
8582
8631
  closedForReason,
8583
8632
  testDelayedChild,
8633
+ calendarEventId,
8634
+ ticketOwner,
8584
8635
  } : {
8585
8636
  assignedTo: string[]
8586
8637
  info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
@@ -8588,6 +8639,8 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8588
8639
  closedForReason?: string,
8589
8640
  enduser?: Enduser,
8590
8641
  testDelayedChild?: boolean,
8642
+ calendarEventId?: string, // copied into journeyContext by close_ticket
8643
+ ticketOwner?: string, // override the seed ticket's owner, to avoid masking a previous-owner fallback
8591
8644
  }) => {
8592
8645
 
8593
8646
  const e = enduser || await sdk.api.endusers.createOne({ assignedTo, journeys: { [journey.id]: '' }, fields: { Tag: "tag3"} })
@@ -8625,8 +8678,9 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8625
8678
  enduserId: e.id,
8626
8679
  automationStepId: PLACEHOLDER_ID,
8627
8680
  journeyId: journey.id,
8628
- owner: validOwners[0],
8681
+ owner: ticketOwner ?? validOwners[0],
8629
8682
  closedForReason,
8683
+ ...(calendarEventId ? { calendarEventId } : {}),
8630
8684
  })
8631
8685
 
8632
8686
  await async_test(
@@ -8759,11 +8813,13 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8759
8813
  info,
8760
8814
  validOwners,
8761
8815
  enduser,
8816
+ journeyContext,
8762
8817
  } : {
8763
8818
  assignedTo: string[]
8764
8819
  info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
8765
8820
  validOwners: string[],
8766
8821
  enduser?: Enduser,
8822
+ journeyContext?: JourneyContext,
8767
8823
  }) => {
8768
8824
  const e = enduser || await sdk.api.endusers.createOne({ assignedTo, fields: { Tag: 'tag3' } })
8769
8825
  await sdk.api.automated_actions.createOne({
@@ -8774,6 +8830,7 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8774
8830
  journeyId: journey.id,
8775
8831
  status: 'active',
8776
8832
  processAfter: Date.now(),
8833
+ ...(journeyContext ? { journeyContext } : {}),
8777
8834
  })
8778
8835
 
8779
8836
  await async_test(
@@ -8874,6 +8931,115 @@ const test_ticket_automation_assignment_and_optimization = async () => {
8874
8931
  validOwners: [sdkNonAdmin.userInfo.id],
8875
8932
  })
8876
8933
 
8934
+ // 'Recently-Booked Appointment Host' assignment
8935
+ // journeyContext.calendarEventId, when it resolves to a host, takes priority over the legacy
8936
+ // "newest event for this enduser" lookup. one enduser per case, to avoid trigger throttling.
8937
+ const HOST_A = sdkNonAdmin.userInfo.id
8938
+ const HOST_B = sdk.userInfo.id
8939
+ const appointmentHostInfo = (defaultAssignee: string) => ({
8940
+ assignmentStrategy: { type: 'Recently-Booked Appointment Host', info: {} } as const,
8941
+ defaultAssignee,
8942
+ })
8943
+ // the legacy filter matches an event having *some* user attendee and *some* attendee with this
8944
+ // enduser's id, so each hosted event carries both
8945
+ const createHostedEvent = (enduserId: string, hostId: string) => sdk.api.calendar_events.createOne({
8946
+ title: 'appointment host test',
8947
+ durationInMinutes: 30,
8948
+ startTimeInMS: Date.now(),
8949
+ attendees: [{ type: 'enduser', id: enduserId }, { type: 'user', id: hostId }],
8950
+ })
8951
+
8952
+ // CORE PROOF: two appointments with different hosts, journey context names the OLDER one.
8953
+ // fails before this fix, which always took the newest-created event (HOST_B).
8954
+ {
8955
+ const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
8956
+ const older = await createHostedEvent(e.id, HOST_A)
8957
+ const newer = await createHostedEvent(e.id, HOST_B) // higher _id, so legacy sort picks this one
8958
+ await testBackgroundTicket({
8959
+ assignedTo: [],
8960
+ enduser: e,
8961
+ info: appointmentHostInfo(HOST_B), // distinct from expected owner, so a fallback would fail
8962
+ validOwners: [HOST_A],
8963
+ journeyContext: { calendarEventId: older.id },
8964
+ })
8965
+ await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
8966
+ }
8967
+
8968
+ // no journey context -> legacy "newest event" behavior is unchanged
8969
+ {
8970
+ const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
8971
+ const older = await createHostedEvent(e.id, HOST_A)
8972
+ const newer = await createHostedEvent(e.id, HOST_B)
8973
+ await testBackgroundTicket({
8974
+ assignedTo: [],
8975
+ enduser: e,
8976
+ info: appointmentHostInfo(HOST_A),
8977
+ validOwners: [HOST_B], // newest created event's host
8978
+ })
8979
+ await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
8980
+ }
8981
+
8982
+ // context event no longer exists -> falls back to the legacy lookup
8983
+ {
8984
+ const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
8985
+ const deleted = await createHostedEvent(e.id, HOST_B)
8986
+ await sdk.api.calendar_events.deleteOne(deleted.id)
8987
+ const hosted = await createHostedEvent(e.id, HOST_A)
8988
+ await testBackgroundTicket({
8989
+ assignedTo: [],
8990
+ enduser: e,
8991
+ info: appointmentHostInfo(HOST_B),
8992
+ validOwners: [HOST_A],
8993
+ journeyContext: { calendarEventId: deleted.id },
8994
+ })
8995
+ await sdk.api.calendar_events.deleteOne(hosted.id)
8996
+ }
8997
+
8998
+ // context event has no user attendee (so yields no host) -> falls back to the legacy lookup
8999
+ {
9000
+ const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
9001
+ const enduserOnly = await sdk.api.calendar_events.createOne({
9002
+ title: 'appointment host test, no host',
9003
+ durationInMinutes: 30,
9004
+ startTimeInMS: Date.now(),
9005
+ attendees: [{ type: 'enduser', id: e.id }],
9006
+ })
9007
+ const hosted = await createHostedEvent(e.id, HOST_A)
9008
+ await testBackgroundTicket({
9009
+ assignedTo: [],
9010
+ enduser: e,
9011
+ info: appointmentHostInfo(HOST_B),
9012
+ validOwners: [HOST_A],
9013
+ journeyContext: { calendarEventId: enduserOnly.id },
9014
+ })
9015
+ await Promise.all([enduserOnly, hosted].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
9016
+ }
9017
+
9018
+ // no events at all -> no resolvable host, so defaultAssignee wins
9019
+ await testBackgroundTicket({
9020
+ assignedTo: [],
9021
+ info: appointmentHostInfo(HOST_A),
9022
+ validOwners: [HOST_A],
9023
+ })
9024
+
9025
+ // api path: close_ticket copies the original ticket's calendarEventId into journeyContext
9026
+ {
9027
+ const e = await sdk.api.endusers.createOne({
9028
+ journeys: { [journey.id]: '' }, fields: { Tag: 'tag3' },
9029
+ })
9030
+ const older = await createHostedEvent(e.id, HOST_A)
9031
+ const newer = await createHostedEvent(e.id, HOST_B)
9032
+ await testForegroundTicket({
9033
+ assignedTo: [],
9034
+ enduser: e,
9035
+ info: appointmentHostInfo(HOST_B),
9036
+ validOwners: [HOST_A],
9037
+ calendarEventId: older.id,
9038
+ ticketOwner: HOST_B, // so a previous-owner style fallback would also fail
9039
+ })
9040
+ await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
9041
+ }
9042
+
8877
9043
  return Promise.all([
8878
9044
  await sdk.api.journeys.deleteOne(journey.id),
8879
9045
  await sdk.api.ticket_queues.deleteOne(queue.id),
@@ -9560,6 +9726,10 @@ export const formsort_tests = async () => {
9560
9726
  if (!r.formsort?.length) { return false }
9561
9727
  if (!r.responses?.length) { return false }
9562
9728
  if (r.responses.length !== answers.length) { return false }
9729
+ // this response attached to a patient that already existed (created at the SDK call above), so
9730
+ // the webhook flags it and email/phone are withheld from the enduser update — see
9731
+ // authFieldIntakeDisabled in api.ts and formsort_webhook_auth_field_lock.test.ts
9732
+ if (!r.authFieldIntakeDisabled) { return false }
9563
9733
 
9564
9734
  if (!validateResponse(r, 'address', JSON.stringify(address, null, 2))) { return false }
9565
9735
  if (!validateResponse(r, 'email', answersEmail)) { return false }
@@ -9588,7 +9758,10 @@ export const formsort_tests = async () => {
9588
9758
  if (submissionEnduser.fname !== 'Fname') { return false }
9589
9759
  if (submissionEnduser.lname !== 'Lname') { return false }
9590
9760
  if (submissionEnduser.gender !== 'Male') { return false }
9591
- if (submissionEnduser.phone !== '+15555555555') { return false }
9761
+ // phone is an auth channel (OTP delivery), and this enduser was matched by a $or guess rather
9762
+ // than created by this submission, so the submitted phone must NOT land. Non-auth intake fields
9763
+ // above still flow through.
9764
+ if (submissionEnduser.phone) { return false }
9592
9765
  if (submissionEnduser.defaultFromEmail !== 'from@tellescope.com') { return false }
9593
9766
  if (submissionEnduser.useDefaultFromEmailInAutomations !== true) { return false }
9594
9767
  if (submissionEnduser.defaultFromPhone !== '+15555555555') { return false }
@@ -13751,6 +13924,55 @@ const replace_form_field_template_values_tests = async () => {
13751
13924
  ) === '<p>Locations: NYC<br />SF<br />LA</p>',
13752
13925
  'fail CRLF in substituted value', 'CRLF in substituted value'
13753
13926
  )
13927
+
13928
+ // escapeHTMLValues: substituted values are patient-supplied (their own intake answers / enduser
13929
+ // record), so on any path rendered by a sanitizer that permits iframes they must be escaped —
13930
+ // otherwise a patient can inject a frame into an admin-authored description.
13931
+ const enduserWithMarkupField = {
13932
+ fname: "Multi",
13933
+ fields: { Injected: '<iframe src="https://evil.example"></iframe>' },
13934
+ } as Partial<Enduser>
13935
+
13936
+ const escaped = replace_form_field_template_values(
13937
+ '<p>{{enduser.Injected}}</p>',
13938
+ { enduser: enduserWithMarkupField, escapeNewlinesAsHTMLBreaks: true, escapeHTMLValues: true }
13939
+ )
13940
+ assert(
13941
+ escaped.includes('&lt;iframe') && !escaped.includes('<iframe'),
13942
+ 'fail escapeHTMLValues escapes markup', 'escapeHTMLValues escapes markup'
13943
+ )
13944
+
13945
+ // Escaping must run BEFORE the newline conversion, or the injected breaks become &lt;br /&gt;
13946
+ const enduserWithMarkupAndNewline = {
13947
+ fields: { Mixed: 'a<b\nc&d' },
13948
+ } as Partial<Enduser>
13949
+ assert(
13950
+ replace_form_field_template_values(
13951
+ '{{enduser.Mixed}}',
13952
+ { enduser: enduserWithMarkupAndNewline, escapeNewlinesAsHTMLBreaks: true, escapeHTMLValues: true }
13953
+ ) === 'a&lt;b<br />c&amp;d',
13954
+ 'fail escapeHTMLValues ordering vs newline breaks', 'escapeHTMLValues ordering vs newline breaks'
13955
+ )
13956
+
13957
+ // Flag absent => byte-identical to today. Plain-text callers (field titles/descriptions rendered
13958
+ // in a Typography) rely on this: escaping there would corrupt e.g. a name containing '&'.
13959
+ const enduserWithAmpersand = {
13960
+ fields: { Practice: 'Smith & Jones <Cardiology>' },
13961
+ } as Partial<Enduser>
13962
+ assert(
13963
+ replace_form_field_template_values(
13964
+ 'Practice: {{enduser.Practice}}',
13965
+ { enduser: enduserWithAmpersand }
13966
+ ) === 'Practice: Smith & Jones <Cardiology>',
13967
+ 'fail no escaping without the flag', 'no escaping without the flag'
13968
+ )
13969
+ assert(
13970
+ replace_form_field_template_values(
13971
+ 'Practice: {{enduser.Practice}}',
13972
+ { enduser: enduserWithAmpersand, escapeHTMLValues: true }
13973
+ ) === 'Practice: Smith &amp; Jones &lt;Cardiology&gt;',
13974
+ 'fail escaping with the flag', 'escaping with the flag'
13975
+ )
13754
13976
  }
13755
13977
 
13756
13978
  const inbox_threads_building_tests = async () => {
@@ -15052,6 +15274,9 @@ const ip_address_form_tests = async () => {
15052
15274
  await replace_form_field_template_values_tests()
15053
15275
  await mfa_tests()
15054
15276
  await setup_tests(sdk, sdkNonAdmin)
15277
+ await allowed_paths_confinement_tests({ sdk, sdkNonAdmin })
15278
+ await phone_calls_conference_hold_tests({ sdk, sdkNonAdmin })
15279
+ await tickets_bulk_assign_care_team_tests({ sdk, sdkNonAdmin })
15055
15280
  await custom_dashboards_tests({ sdk, sdkNonAdmin })
15056
15281
  await register_business_id_injection_tests({ sdk, sdkNonAdmin })
15057
15282
  await automation_trigger_tests()
@@ -15060,10 +15285,13 @@ const ip_address_form_tests = async () => {
15060
15285
  await beluga_pharmacy_mappings_tests({ sdk, sdkNonAdmin })
15061
15286
  await mdi_case_offerings_tests({ sdk, sdkNonAdmin })
15062
15287
  await enduser_write_restrictions_tests({ sdk, sdkNonAdmin })
15288
+ await enduser_ai_summary_trust_gate_tests({ sdk, sdkNonAdmin })
15063
15289
  await enduser_cross_create_tests({ sdk, sdkNonAdmin })
15064
15290
  await globalunique_leak_tests({ sdk, sdkNonAdmin })
15065
15291
  await load_inbox_redaction_tests({ sdk, sdkNonAdmin })
15066
15292
  await webhook_timeout_tests({ sdk, sdkNonAdmin })
15293
+ await public_endpoint_auth_field_lock_tests({ sdk, sdkNonAdmin })
15294
+ await formsort_webhook_auth_field_lock_tests({ sdk, sdkNonAdmin })
15067
15295
  await user_portal_settings_tests({ sdk, sdkNonAdmin })
15068
15296
  await invite_user_enumeration_tests({ sdk, sdkNonAdmin })
15069
15297
  await handle_incoming_communication_cross_tenant_tests({ sdk, sdkNonAdmin })
@@ -15076,6 +15304,7 @@ const ip_address_form_tests = async () => {
15076
15304
  await cascade_role_rename_cross_tenant_tests({ sdk, sdkNonAdmin })
15077
15305
  await self_admin_role_assignment_tests({ sdk, sdkNonAdmin })
15078
15306
  await sanitize_user_html_xss_tests()
15307
+ await sanitize_user_html_with_iframes_tests()
15079
15308
  await prototype_pollution_tests()
15080
15309
  await account_switcher_tests({ sdk, sdkNonAdmin })
15081
15310
  await totp_mfa_tests({ sdk, sdkNonAdmin })
@@ -15110,6 +15339,7 @@ const ip_address_form_tests = async () => {
15110
15339
  await time_tracks_appointment_duration_tests({ sdk, sdkNonAdmin })
15111
15340
  await calendar_event_limits_tests({ sdk, sdkNonAdmin })
15112
15341
  await get_some_projection_tests({ sdk, sdkNonAdmin })
15342
+ await file_download_unicode_names_tests({ sdk, sdkNonAdmin })
15113
15343
  await elation_user_id_tests({ sdk, sdkNonAdmin })
15114
15344
  await concurrent_build_threads_tests({ sdk, sdkNonAdmin })
15115
15345
  await custom_aggregation_tests({ sdk, sdkNonAdmin })
Binary file
package/.env DELETED
@@ -1,13 +0,0 @@
1
- TEST_EMAIL='sebastian.a.coates+test@gmail.com'
2
- MFA_EMAIL='sebass+mfa@tellescope.com'
3
- SUB_EMAIL='sebass+sub@tellescope.com'
4
- OTHER_SUB_EMAIL='sebass+othersub@tellescope.com'
5
- SUB_SUB_EMAIL='sebass+subsub@tellescope.com'
6
- TEST_PASSWORD='asdfGHJK1234!@#$'
7
- TEST_EMAIL_2='sebass192@gmail.com'
8
- TEST_PASSWORD_2='3009SwagSwag'
9
- NON_ADMIN_EMAIL='nonadmin@tellescope.com'
10
- NON_ADMIN_PASSWORD='3009SwagSwag'
11
- CROSS_ORG_API_KEY='cross-org-testing-api-key-manually-set'
12
- CROSS_ORG_TARGET_BUSINESS_ID='69efea9b0c4d5bd1da90ca77'
13
- CROSS_ORG_UNAPPROVED_BUSINESS_ID='63a0d6be40e38ba9e899a490'