@tellescope/sdk 2.0.0-beta.1 → 2.0.0-beta.2

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 (293) hide show
  1. package/CLAUDE.md +24 -0
  2. package/env.example +6 -0
  3. package/lib/cjs/sdk.d.ts +373 -3
  4. package/lib/cjs/sdk.d.ts.map +1 -1
  5. package/lib/cjs/sdk.js.map +1 -1
  6. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts +22 -0
  7. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts.map +1 -0
  8. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.js +506 -0
  9. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.js.map +1 -0
  10. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts +3 -1
  11. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -1
  12. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js +3 -1
  13. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js.map +1 -1
  14. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts +3 -1
  15. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -1
  16. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js +3 -1
  17. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -1
  18. package/lib/cjs/tests/api_tests/form_vital_reading.test.d.ts +7 -0
  19. package/lib/cjs/tests/api_tests/form_vital_reading.test.d.ts.map +1 -0
  20. package/lib/cjs/tests/api_tests/form_vital_reading.test.js +461 -0
  21. package/lib/cjs/tests/api_tests/form_vital_reading.test.js.map +1 -0
  22. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.d.ts +26 -0
  23. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.d.ts.map +1 -0
  24. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.js +232 -0
  25. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.js.map +1 -0
  26. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.d.ts +34 -0
  27. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.d.ts.map +1 -0
  28. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.js +243 -0
  29. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.js.map +1 -0
  30. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts +3 -1
  31. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -1
  32. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js +3 -1
  33. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js.map +1 -1
  34. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +3 -1
  35. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -1
  36. package/lib/cjs/tests/api_tests/organization_signup.test.js +3 -1
  37. package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -1
  38. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.d.ts +7 -0
  39. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.d.ts.map +1 -0
  40. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.js +571 -0
  41. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.js.map +1 -0
  42. package/lib/cjs/tests/api_tests/programs.test.d.ts +3 -1
  43. package/lib/cjs/tests/api_tests/programs.test.d.ts.map +1 -1
  44. package/lib/cjs/tests/api_tests/programs.test.js +73 -17
  45. package/lib/cjs/tests/api_tests/programs.test.js.map +1 -1
  46. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.d.ts +7 -0
  47. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.d.ts.map +1 -0
  48. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.js +733 -0
  49. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.js.map +1 -0
  50. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +3 -1
  51. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -1
  52. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +3 -1
  53. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -1
  54. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +3 -1
  55. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -1
  56. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js +3 -1
  57. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -1
  58. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts +3 -1
  59. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -1
  60. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js +3 -1
  61. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -1
  62. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts +3 -1
  63. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -1
  64. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js +3 -1
  65. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -1
  66. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -1
  67. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +424 -39
  68. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -1
  69. package/lib/cjs/tests/api_tests/security/register_business_id_injection.test.js +2 -2
  70. package/lib/cjs/tests/api_tests/security/register_business_id_injection.test.js.map +1 -1
  71. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +3 -1
  72. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -1
  73. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +3 -1
  74. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -1
  75. package/lib/cjs/tests/api_tests/session_for_join_link.test.d.ts +10 -0
  76. package/lib/cjs/tests/api_tests/session_for_join_link.test.d.ts.map +1 -0
  77. package/lib/cjs/tests/api_tests/session_for_join_link.test.js +147 -0
  78. package/lib/cjs/tests/api_tests/session_for_join_link.test.js.map +1 -0
  79. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
  80. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +191 -88
  81. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
  82. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.d.ts +17 -0
  83. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.d.ts.map +1 -0
  84. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.js +219 -0
  85. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.js.map +1 -0
  86. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.d.ts +5 -0
  87. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.d.ts.map +1 -0
  88. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.js +396 -0
  89. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.js.map +1 -0
  90. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +3 -1
  91. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -1
  92. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +3 -1
  93. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -1
  94. package/lib/cjs/tests/api_tests/totp_mfa.test.d.ts.map +1 -1
  95. package/lib/cjs/tests/api_tests/totp_mfa.test.js +37 -31
  96. package/lib/cjs/tests/api_tests/totp_mfa.test.js.map +1 -1
  97. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts +3 -1
  98. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -1
  99. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js +3 -1
  100. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js.map +1 -1
  101. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts +3 -1
  102. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -1
  103. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js +3 -1
  104. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -1
  105. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts +3 -1
  106. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -1
  107. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js +3 -1
  108. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js.map +1 -1
  109. package/lib/cjs/tests/api_tests/vitl_push.test.d.ts +7 -0
  110. package/lib/cjs/tests/api_tests/vitl_push.test.d.ts.map +1 -0
  111. package/lib/cjs/tests/api_tests/vitl_push.test.js +341 -0
  112. package/lib/cjs/tests/api_tests/vitl_push.test.js.map +1 -0
  113. package/lib/cjs/tests/setup_isolated.d.ts +48 -0
  114. package/lib/cjs/tests/setup_isolated.d.ts.map +1 -1
  115. package/lib/cjs/tests/setup_isolated.js +119 -1
  116. package/lib/cjs/tests/setup_isolated.js.map +1 -1
  117. package/lib/cjs/tests/tests.d.ts.map +1 -1
  118. package/lib/cjs/tests/tests.js +374 -292
  119. package/lib/cjs/tests/tests.js.map +1 -1
  120. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.d.ts +2 -0
  121. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.d.ts.map +1 -0
  122. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.js +201 -0
  123. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.js.map +1 -0
  124. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.d.ts +3 -0
  125. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.d.ts.map +1 -0
  126. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.js +120 -0
  127. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.js.map +1 -0
  128. package/lib/cjs/tests/webhooks_tests.js +44 -34
  129. package/lib/cjs/tests/webhooks_tests.js.map +1 -1
  130. package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  131. package/lib/esm/sdk.d.ts +373 -3
  132. package/lib/esm/sdk.d.ts.map +1 -1
  133. package/lib/esm/sdk.js.map +1 -1
  134. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts +22 -0
  135. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts.map +1 -0
  136. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.js +501 -0
  137. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.js.map +1 -0
  138. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts +3 -1
  139. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -1
  140. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js +2 -0
  141. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js.map +1 -1
  142. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts +3 -1
  143. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -1
  144. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js +2 -0
  145. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -1
  146. package/lib/esm/tests/api_tests/form_vital_reading.test.d.ts +7 -0
  147. package/lib/esm/tests/api_tests/form_vital_reading.test.d.ts.map +1 -0
  148. package/lib/esm/tests/api_tests/form_vital_reading.test.js +456 -0
  149. package/lib/esm/tests/api_tests/form_vital_reading.test.js.map +1 -0
  150. package/lib/esm/tests/api_tests/journey_statistics_cache.test.d.ts +26 -0
  151. package/lib/esm/tests/api_tests/journey_statistics_cache.test.d.ts.map +1 -0
  152. package/lib/esm/tests/api_tests/journey_statistics_cache.test.js +227 -0
  153. package/lib/esm/tests/api_tests/journey_statistics_cache.test.js.map +1 -0
  154. package/lib/esm/tests/api_tests/journey_step_manual_count.test.d.ts +34 -0
  155. package/lib/esm/tests/api_tests/journey_step_manual_count.test.d.ts.map +1 -0
  156. package/lib/esm/tests/api_tests/journey_step_manual_count.test.js +238 -0
  157. package/lib/esm/tests/api_tests/journey_step_manual_count.test.js.map +1 -0
  158. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts +3 -1
  159. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -1
  160. package/lib/esm/tests/api_tests/load_threads_care_team.test.js +2 -0
  161. package/lib/esm/tests/api_tests/load_threads_care_team.test.js.map +1 -1
  162. package/lib/esm/tests/api_tests/organization_signup.test.d.ts +3 -1
  163. package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -1
  164. package/lib/esm/tests/api_tests/organization_signup.test.js +2 -0
  165. package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -1
  166. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.d.ts +7 -0
  167. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.d.ts.map +1 -0
  168. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.js +566 -0
  169. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.js.map +1 -0
  170. package/lib/esm/tests/api_tests/programs.test.d.ts +3 -1
  171. package/lib/esm/tests/api_tests/programs.test.d.ts.map +1 -1
  172. package/lib/esm/tests/api_tests/programs.test.js +72 -16
  173. package/lib/esm/tests/api_tests/programs.test.js.map +1 -1
  174. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.d.ts +7 -0
  175. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.d.ts.map +1 -0
  176. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.js +728 -0
  177. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.js.map +1 -0
  178. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +3 -1
  179. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -1
  180. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +2 -0
  181. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -1
  182. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +3 -1
  183. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -1
  184. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js +2 -0
  185. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -1
  186. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts +3 -1
  187. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -1
  188. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js +2 -0
  189. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -1
  190. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts +3 -1
  191. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -1
  192. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js +2 -0
  193. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -1
  194. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -1
  195. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +424 -39
  196. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -1
  197. package/lib/esm/tests/api_tests/security/register_business_id_injection.test.js +2 -2
  198. package/lib/esm/tests/api_tests/security/register_business_id_injection.test.js.map +1 -1
  199. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +3 -1
  200. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -1
  201. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +2 -0
  202. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -1
  203. package/lib/esm/tests/api_tests/session_for_join_link.test.d.ts +10 -0
  204. package/lib/esm/tests/api_tests/session_for_join_link.test.d.ts.map +1 -0
  205. package/lib/esm/tests/api_tests/session_for_join_link.test.js +142 -0
  206. package/lib/esm/tests/api_tests/session_for_join_link.test.js.map +1 -0
  207. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
  208. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +191 -88
  209. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
  210. package/lib/esm/tests/api_tests/stripe_checkout_items.test.d.ts +17 -0
  211. package/lib/esm/tests/api_tests/stripe_checkout_items.test.d.ts.map +1 -0
  212. package/lib/esm/tests/api_tests/stripe_checkout_items.test.js +214 -0
  213. package/lib/esm/tests/api_tests/stripe_checkout_items.test.js.map +1 -0
  214. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.d.ts +5 -0
  215. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.d.ts.map +1 -0
  216. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.js +388 -0
  217. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.js.map +1 -0
  218. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +3 -1
  219. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -1
  220. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +2 -0
  221. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -1
  222. package/lib/esm/tests/api_tests/totp_mfa.test.d.ts.map +1 -1
  223. package/lib/esm/tests/api_tests/totp_mfa.test.js +37 -31
  224. package/lib/esm/tests/api_tests/totp_mfa.test.js.map +1 -1
  225. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts +3 -1
  226. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -1
  227. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js +2 -0
  228. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js.map +1 -1
  229. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts +3 -1
  230. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -1
  231. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js +2 -0
  232. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -1
  233. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts +3 -1
  234. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -1
  235. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js +2 -0
  236. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js.map +1 -1
  237. package/lib/esm/tests/api_tests/vitl_push.test.d.ts +7 -0
  238. package/lib/esm/tests/api_tests/vitl_push.test.d.ts.map +1 -0
  239. package/lib/esm/tests/api_tests/vitl_push.test.js +333 -0
  240. package/lib/esm/tests/api_tests/vitl_push.test.js.map +1 -0
  241. package/lib/esm/tests/setup_isolated.d.ts +48 -0
  242. package/lib/esm/tests/setup_isolated.d.ts.map +1 -1
  243. package/lib/esm/tests/setup_isolated.js +114 -0
  244. package/lib/esm/tests/setup_isolated.js.map +1 -1
  245. package/lib/esm/tests/tests.d.ts.map +1 -1
  246. package/lib/esm/tests/tests.js +375 -293
  247. package/lib/esm/tests/tests.js.map +1 -1
  248. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.d.ts +2 -0
  249. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.d.ts.map +1 -0
  250. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.js +174 -0
  251. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.js.map +1 -0
  252. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.d.ts +3 -0
  253. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.d.ts.map +1 -0
  254. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.js +117 -0
  255. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.js.map +1 -0
  256. package/lib/esm/tests/webhooks_tests.js +44 -34
  257. package/lib/esm/tests/webhooks_tests.js.map +1 -1
  258. package/lib/tsconfig.tsbuildinfo +1 -1
  259. package/package.json +12 -11
  260. package/src/sdk.ts +3 -2
  261. package/src/tests/api_tests/booking_prevent_upcoming_duplicate.test.ts +541 -0
  262. package/src/tests/api_tests/enduser_fields_sync.test.ts +4 -0
  263. package/src/tests/api_tests/enduser_observations_auto_review.test.ts +4 -0
  264. package/src/tests/api_tests/form_vital_reading.test.ts +393 -0
  265. package/src/tests/api_tests/journey_statistics_cache.test.ts +237 -0
  266. package/src/tests/api_tests/journey_step_manual_count.test.ts +220 -0
  267. package/src/tests/api_tests/load_threads_care_team.test.ts +4 -0
  268. package/src/tests/api_tests/organization_signup.test.ts +4 -0
  269. package/src/tests/api_tests/phone_embed_leave_message.test.ts +541 -0
  270. package/src/tests/api_tests/programs.test.ts +351 -0
  271. package/src/tests/api_tests/public_form_case_insensitive_match.test.ts +632 -0
  272. package/src/tests/api_tests/security/add-to-journey-foreign-enduserid.test.ts +4 -0
  273. package/src/tests/api_tests/security/appointment-reschedule-ownership.test.ts +4 -0
  274. package/src/tests/api_tests/security/file-link-session-confinement.test.ts +4 -0
  275. package/src/tests/api_tests/security/form-logic-js-execution.test.ts +4 -0
  276. package/src/tests/api_tests/security/formsort_webhook_auth_field_lock.test.ts +225 -0
  277. package/src/tests/api_tests/security/register_business_id_injection.test.ts +2 -2
  278. package/src/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.ts +4 -0
  279. package/src/tests/api_tests/session_for_join_link.test.ts +99 -0
  280. package/src/tests/api_tests/set_fields_order_templates.test.ts +94 -9
  281. package/src/tests/api_tests/stripe_checkout_items.test.ts +186 -0
  282. package/src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts +4 -0
  283. package/src/tests/api_tests/totp_mfa.test.ts +10 -5
  284. package/src/tests/api_tests/trigger_contains_matching.test.ts +4 -0
  285. package/src/tests/api_tests/user_logs_restore_deleted.test.ts +4 -0
  286. package/src/tests/api_tests/vital_review_trigger_action.test.ts +455 -0
  287. package/src/tests/api_tests/vitl_push.test.ts +323 -0
  288. package/src/tests/setup_isolated.ts +140 -0
  289. package/src/tests/tests.ts +124 -30
  290. package/src/tests/unit_tests/isolated_suite_discovery.test.ts +178 -0
  291. package/src/tests/unit_tests/programs_conditional_logic.test.ts +189 -0
  292. package/src/tests/webhooks_tests.ts +6 -1
  293. package/test_generated.pdf +0 -0
@@ -0,0 +1,323 @@
1
+ require('source-map-support').install();
2
+
3
+ import axios from "axios"
4
+
5
+ import {
6
+ assert_checks,
7
+ Check,
8
+ setup_isolated_tenant,
9
+ IsolatedSuite,
10
+ } from "../setup_isolated"
11
+ import { VITL_TITLE } from "@tellescope/constants"
12
+
13
+ const host = process.env.API_URL || 'http://localhost:8080' as const
14
+
15
+ /**
16
+ * VITL eRx patient push — endpoint-level coverage (CU-86e3cua4p).
17
+ *
18
+ * Everything here is verifiable WITHOUT reaching the real vendor, which matters because the payload is
19
+ * patient PHI and the only endpoint we have is a live tenant webhook. The connected case points at a
20
+ * host in the reserved `.invalid` TLD, so DNS never resolves and no data leaves the machine — but the
21
+ * request is attempted, which is exactly what makes the failure path assertable.
22
+ *
23
+ * What this locks:
24
+ * A. an org that has NOT connected VITL cannot push at all, and writes no integration_logs row (the
25
+ * PHI-egress gate — QA step 12)
26
+ * B. a patient missing a required field fails PRE-FLIGHT: the error names the field AND no
27
+ * integration_logs row is written, proving no request was sent (QA step 9)
28
+ * C. a complete patient produces an Error row whose stored payload has dateOfBirth in YYYY-MM-DD,
29
+ * uid == the enduser id, no gender key for an unmappable gender, and no trace of the shared
30
+ * secret anywhere in the row (QA steps 5, 6, 8, 10)
31
+ * D. a failed push writes nothing to the enduser — no lastVITLSyncAt, no VITL reference (QA step 10)
32
+ * E. the pushVITLPatient journey action reaches the same code path (QA step 11) — the automation step
33
+ * round-trips through validation, and automated_actions.process runs the VITL branch rather than
34
+ * falling through to `unimplemented`
35
+ *
36
+ * The 2xx path (QA steps 4 and 7) needs real VITL credentials and stays manual.
37
+ */
38
+
39
+ // Reserved TLD (RFC 2606): guaranteed never to resolve, so the push fails at connect time.
40
+ const UNREACHABLE_WEBHOOK_URL = 'https://vitl-push-test.invalid/api/webhooks/test'
41
+ const SHARED_SECRET = 'vitl-shared-secret-must-never-be-logged'
42
+
43
+ export const vitl_push_checks = async (): Promise<Check[]> => {
44
+ const checks: Check[] = []
45
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
46
+
47
+ let provisioned: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
48
+
49
+ const push = async (enduserId: string) => {
50
+ try {
51
+ const result = await provisioned!.sdk.api.endusers.push({ enduserId, destinations: [VITL_TITLE] })
52
+ return { ok: true, message: JSON.stringify(result) }
53
+ } catch (err: any) {
54
+ return { ok: false, message: err?.message || err?.toString() || '' }
55
+ }
56
+ }
57
+
58
+ const vitlLogs = async () => (
59
+ (await provisioned!.sdk.api.integration_logs.getSome({ filter: { integration: VITL_TITLE } as any })) ?? []
60
+ )
61
+
62
+ try {
63
+ provisioned = await setup_isolated_tenant()
64
+ const { sdk } = provisioned
65
+
66
+ const completeEnduser = await sdk.api.endusers.createOne({
67
+ fname: 'Vitl', lname: 'Push',
68
+ email: `vitl_push_${Date.now()}@test.tellescope.com`,
69
+ phone: '+15555550123',
70
+ dateOfBirth: '12-10-1815',
71
+ gender: 'Other', // deliberately unmappable — must be omitted rather than forwarded
72
+ addressLineOne: '1 Analytical Way',
73
+ city: 'London', state: 'NY',
74
+ zipCode: '10001-6789', // stored with a +4; only the 5 digits may reach VITL
75
+ })
76
+
77
+ /* ===== A: no VITL connection => no push, no PHI egress ===== */
78
+
79
+ const beforeConnect = await push(completeEnduser.id)
80
+ record(
81
+ 'A1: push is rejected when the org has not connected VITL',
82
+ !beforeConnect.ok && /not connected/i.test(beforeConnect.message),
83
+ `ok=${beforeConnect.ok} message=${beforeConnect.message}`,
84
+ )
85
+ record(
86
+ 'A2: no integration_logs row is written when VITL is not connected',
87
+ (await vitlLogs()).length === 0,
88
+ `rows=${(await vitlLogs()).length}`,
89
+ )
90
+
91
+ /* ===== connect VITL (URL is unreachable by construction) ===== */
92
+
93
+ const connectResponse = await sdk.api.integrations.add_api_key_integration({
94
+ integration: VITL_TITLE,
95
+ API_KEY: SHARED_SECRET,
96
+ environment: UNREACHABLE_WEBHOOK_URL,
97
+ })
98
+
99
+ // The connect response is not redacted by the generic read path — insertOne returns the inserted
100
+ // document verbatim, so the VITL branch runs it through redactIntegration explicitly. Asserted on the whole serialized body, not on a named field.
101
+ record(
102
+ 'A0: the connect response does not echo the shared secret back to the client',
103
+ !JSON.stringify(connectResponse ?? {}).includes(SHARED_SECRET),
104
+ `response=${JSON.stringify(connectResponse)}`,
105
+ )
106
+
107
+ const org = await sdk.api.organizations.getOne(provisioned.businessId)
108
+ record(
109
+ 'A3: connecting VITL sets hasConnectedVITL on the organization',
110
+ !!(org as any).hasConnectedVITL,
111
+ `hasConnectedVITL=${(org as any).hasConnectedVITL}`,
112
+ )
113
+
114
+ // The secret must not be readable back through the integrations model.
115
+ const redacted = await sdk.api.integrations.load_redacted({})
116
+ const vitlRedacted = redacted.integrations.find((i: any) => i.title === VITL_TITLE)
117
+ record(
118
+ 'A4: the shared secret is not returned by load_redacted',
119
+ !!vitlRedacted && !JSON.stringify(vitlRedacted).includes(SHARED_SECRET),
120
+ `integration=${JSON.stringify(vitlRedacted)}`,
121
+ )
122
+ record(
123
+ 'A5: the webhook URL is stored and readable (it is configuration, not a credential)',
124
+ vitlRedacted?.environment === UNREACHABLE_WEBHOOK_URL,
125
+ `environment=${vitlRedacted?.environment}`,
126
+ )
127
+
128
+ /* ===== B: pre-flight failure sends no request ===== */
129
+
130
+ const incompleteEnduser = await sdk.api.endusers.createOne({
131
+ fname: 'Vitl', lname: 'Incomplete',
132
+ email: `vitl_incomplete_${Date.now()}@test.tellescope.com`,
133
+ phone: '+15555550124',
134
+ dateOfBirth: '12-10-1815',
135
+ addressLineOne: '1 Analytical Way',
136
+ // city deliberately omitted
137
+ state: 'NY', zipCode: '10001',
138
+ })
139
+
140
+ const incompleteResult = await push(incompleteEnduser.id)
141
+ record(
142
+ 'B1: a patient missing a required field fails with a message naming that field',
143
+ !incompleteResult.ok && incompleteResult.message.includes('City'),
144
+ `ok=${incompleteResult.ok} message=${incompleteResult.message}`,
145
+ )
146
+ record(
147
+ 'B2: a pre-flight failure writes NO integration_logs row (no request was sent)',
148
+ (await vitlLogs()).length === 0,
149
+ `rows=${(await vitlLogs()).length} (expected 0)`,
150
+ )
151
+
152
+ // VITL also rejects the whole request on an unusable zip. (The matching email-shape check is
153
+ // asserted in the unit tests instead: endusers.email is validated by our own schema on both create
154
+ // and update, so a malformed address cannot be stored through the API to push in the first place.)
155
+ const badZipEnduser = await sdk.api.endusers.createOne({
156
+ fname: 'Vitl', lname: 'BadZip',
157
+ email: `vitl_bad_zip_${Date.now()}@test.tellescope.com`,
158
+ phone: '+15555550126', dateOfBirth: '12-10-1815',
159
+ addressLineOne: '1 Analytical Way', city: 'London', state: 'NY',
160
+ zipCode: 'NW1 6XE', // no 5-digit zip is derivable
161
+ })
162
+ const badZipResult = await push(badZipEnduser.id)
163
+ record(
164
+ 'B3: a zip with no 5 digits fails pre-flight with a message naming ZIP',
165
+ !badZipResult.ok && /zip/i.test(badZipResult.message),
166
+ `ok=${badZipResult.ok} message=${badZipResult.message}`,
167
+ )
168
+
169
+ record(
170
+ 'B4: none of the pre-flight failures sent a request',
171
+ (await vitlLogs()).length === 0,
172
+ `rows=${(await vitlLogs()).length} (expected 0)`,
173
+ )
174
+
175
+ /* ===== C: a real attempt is logged, correctly and without the secret ===== */
176
+
177
+ const completeResult = await push(completeEnduser.id)
178
+ record(
179
+ 'C1: an unreachable webhook reports a transport failure, not a bogus field rejection',
180
+ !completeResult.ok && /could not reach vitl/i.test(completeResult.message),
181
+ `ok=${completeResult.ok} message=${completeResult.message}`,
182
+ )
183
+
184
+ const logs = await vitlLogs()
185
+ const log: any = logs[0]
186
+ record(
187
+ 'C2: exactly one integration_logs row is written for the attempt',
188
+ logs.length === 1,
189
+ `rows=${logs.length}`,
190
+ )
191
+ record(
192
+ 'C3: the row is integration=VITL, type=Create Patient, status=Error, with the enduserId',
193
+ !!log && log.integration === VITL_TITLE && log.type === 'Create Patient'
194
+ && log.status === 'Error' && log.enduserId === completeEnduser.id,
195
+ `row=${JSON.stringify({ integration: log?.integration, type: log?.type, status: log?.status, enduserId: log?.enduserId })}`,
196
+ )
197
+ record(
198
+ 'C4: the logged url is the configured webhook URL',
199
+ log?.url === UNREACHABLE_WEBHOOK_URL,
200
+ `url=${log?.url}`,
201
+ )
202
+
203
+ let payload: any = undefined
204
+ try { payload = JSON.parse(log?.payload ?? '{}') } catch {}
205
+ record(
206
+ 'C5: the logged payload has dateOfBirth in YYYY-MM-DD, not our stored MM-DD-YYYY',
207
+ payload?.dateOfBirth === '1815-12-10',
208
+ `dateOfBirth=${payload?.dateOfBirth} (stored value is 12-10-1815)`,
209
+ )
210
+ record(
211
+ 'C6: the logged payload uid is the Tellescope enduser id',
212
+ payload?.uid === completeEnduser.id,
213
+ `uid=${payload?.uid} enduserId=${completeEnduser.id}`,
214
+ )
215
+ record(
216
+ 'C6b: a stored ZIP+4 is narrowed to the 5-digit zipcode',
217
+ payload?.zipcode === '10001',
218
+ `zipcode=${payload?.zipcode} (stored zipCode is 10001-6789)`,
219
+ )
220
+ record(
221
+ 'C7: an unmappable gender is omitted from the payload, not forwarded',
222
+ !!payload && !('gender' in payload),
223
+ `gender=${JSON.stringify(payload?.gender)} (enduser gender is 'Other')`,
224
+ )
225
+ record(
226
+ 'C8: the x-vitl-secret value appears NOWHERE in the integration_logs row',
227
+ !JSON.stringify(log ?? {}).includes(SHARED_SECRET),
228
+ 'searched the whole serialized row for the shared secret',
229
+ )
230
+
231
+ /* ===== D: a failed push leaves the enduser untouched ===== */
232
+
233
+ const afterFailure: any = await sdk.api.endusers.getOne(completeEnduser.id)
234
+ record(
235
+ 'D1: a failed push does not set lastVITLSyncAt',
236
+ !afterFailure.lastVITLSyncAt,
237
+ `lastVITLSyncAt=${afterFailure.lastVITLSyncAt}`,
238
+ )
239
+ record(
240
+ 'D2: a failed push does not add a VITL reference to the enduser',
241
+ !afterFailure.references?.find((r: any) => r.type === VITL_TITLE),
242
+ `references=${JSON.stringify(afterFailure.references ?? [])}`,
243
+ )
244
+
245
+ /* ===== E: the journey action reaches the same code path ===== */
246
+
247
+ const journey = await sdk.api.journeys.createOne({ title: 'VITL push journey' })
248
+ const step = await sdk.api.automation_steps.createOne({
249
+ journeyId: journey.id,
250
+ events: [{ type: 'onJourneyStart', info: {} }],
251
+ action: { type: 'pushVITLPatient', info: {} },
252
+ })
253
+ record(
254
+ 'E1: a pushVITLPatient automation step round-trips through validation',
255
+ step.action.type === 'pushVITLPatient',
256
+ `action=${JSON.stringify(step.action)}`,
257
+ )
258
+
259
+ // processAfter is an hour out so the live worker cannot claim the row before the explicit call.
260
+ const automatedAction = await sdk.api.automated_actions.createOne({
261
+ enduserId: completeEnduser.id,
262
+ journeyId: journey.id,
263
+ automationStepId: step.id,
264
+ event: { type: 'onJourneyStart', info: {} },
265
+ action: { type: 'pushVITLPatient', info: {} },
266
+ status: 'active',
267
+ processAfter: Date.now() + 60 * 60 * 1000,
268
+ } as any)
269
+
270
+ const adminToken = (await sdk.api.users.generate_auth_token({ id: sdk.userInfo.id })).authToken
271
+ const processed = await axios.request({
272
+ method: 'post',
273
+ url: `${host}/v1/automated-actions/process`,
274
+ data: { automatedActionId: automatedAction.id },
275
+ headers: { Authorization: `Bearer ${adminToken}` },
276
+ validateStatus: () => true,
277
+ })
278
+
279
+ // `unimplemented` would mean the action type never reached a branch — i.e. the journey action is
280
+ // declared but not wired. It must instead fail the same way the manual push does.
281
+ record(
282
+ 'E2: process runs the VITL branch (not unimplemented) and reports the vendor failure',
283
+ processed.status === 200
284
+ && processed.data?.unimplemented !== true
285
+ && processed.data?.success === false
286
+ && /VITL/i.test(processed.data?.error ?? ''),
287
+ `status=${processed.status} body=${JSON.stringify(processed.data)}`,
288
+ )
289
+
290
+ record(
291
+ 'E3: the journey action produced a second integration_logs row, same as the manual push',
292
+ (await vitlLogs()).length === 2,
293
+ `rows=${(await vitlLogs()).length} (expected 2)`,
294
+ )
295
+
296
+ } finally {
297
+ if (provisioned) { try { await provisioned.teardown() } catch (e) { console.error('teardown failed', e) } }
298
+ }
299
+
300
+ return checks
301
+ }
302
+
303
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
304
+ export const vitl_push_tests = async () => {
305
+ assert_checks('VITL patient push', await vitl_push_checks())
306
+ }
307
+
308
+ /** Enrolls this suite in the auto-discovered parallel pool — see run_discovered_isolated_suites. */
309
+ export const ISOLATED_SUITE: IsolatedSuite = { name: 'VITL patient push', run: vitl_push_checks }
310
+
311
+ // Allow running this test file independently
312
+ if (require.main === module) {
313
+ console.log(`🌐 Using API URL: ${host}`)
314
+ vitl_push_tests()
315
+ .then(() => {
316
+ console.log("✅ VITL patient push test suite completed successfully")
317
+ process.exit(0)
318
+ })
319
+ .catch((error) => {
320
+ console.error("❌ VITL patient push test suite failed:", error)
321
+ process.exit(1)
322
+ })
323
+ }
@@ -1,4 +1,6 @@
1
1
  import axios from "axios"
2
+ import fs from "fs"
3
+ import path from "path"
2
4
 
3
5
  import { assert, log_header } from "@tellescope/testing"
4
6
 
@@ -178,3 +180,141 @@ export const run_isolated_in_parallel = async (
178
180
  assert_checks(suites[i].name, r.value)
179
181
  }
180
182
  }
183
+
184
+ /* ===================== auto-discovered parallel dispatcher ===================== */
185
+
186
+ /**
187
+ * A suite enrolled in the parallel pool. A test file enrolls itself by exporting one of these as
188
+ * `ISOLATED_SUITE`; nothing shared has to be edited:
189
+ *
190
+ * export const ISOLATED_SUITE: IsolatedSuite = { name: 'Programs', run: programs_checks }
191
+ *
192
+ * `run` must obey the same contract as run_isolated_in_parallel's entries: collect Check[], tear
193
+ * down its OWN tenants in a finally, and never assert (assert calls process.exit, which skips every
194
+ * other in-flight suite's teardown and strands its tenants permanently).
195
+ */
196
+ export type IsolatedSuite = {
197
+ /** Unique across all enrolled suites; printed as the batch header and the check-table title. */
198
+ name: string,
199
+ run: () => Promise<Check[]>,
200
+ }
201
+
202
+ /** The export name a test file uses to enroll. Exported so the discovery unit test can scan for it. */
203
+ export const ISOLATED_SUITE_EXPORT = 'ISOLATED_SUITE'
204
+
205
+ /** Suites in flight at once when TEST_PARALLELISM is unset. Past this, a local API starts returning
206
+ * 429s from the per-record update throttles rather than real failures. */
207
+ export const DEFAULT_TEST_PARALLELISM = 8
208
+
209
+ /** Positive integers only; anything else falls back to the default rather than failing a run (a bad
210
+ * value must never mean "no limit"). */
211
+ export const parse_test_parallelism = (raw: string | undefined): number => {
212
+ if (raw === undefined || raw.trim() === '') return DEFAULT_TEST_PARALLELISM
213
+
214
+ const parsed = Number(raw.trim())
215
+ if (!Number.isInteger(parsed) || parsed < 1) {
216
+ console.warn(
217
+ ` TEST_PARALLELISM=${JSON.stringify(raw)} is not a positive integer — using the default of ${DEFAULT_TEST_PARALLELISM}`
218
+ )
219
+ return DEFAULT_TEST_PARALLELISM
220
+ }
221
+ return parsed
222
+ }
223
+
224
+ export type DiscoveredIsolatedSuite = IsolatedSuite & {
225
+ /** Path relative to the api_tests root, e.g. `security/form-logic-js-execution.test.js`. */
226
+ file: string,
227
+ }
228
+
229
+ /** The compiled api_tests tree. Discovery walks `lib/cjs`, never `src` — a stale build silently drops
230
+ * a suite from the run, which is what `npm run check:build` (run before every suite) exists to catch. */
231
+ export const isolated_suites_root = () => path.join(__dirname, 'api_tests')
232
+
233
+ /**
234
+ * Relative paths of every compiled test file under `root`, recursively (so `security/` is included),
235
+ * sorted by a plain code-unit comparison — NOT localeCompare, so batch membership is identical on
236
+ * every machine.
237
+ */
238
+ const compiled_test_files = (root: string): string[] => {
239
+ const found: string[] = []
240
+
241
+ const walk = (dir: string, prefix: string) => {
242
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
243
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name
244
+
245
+ if (entry.isDirectory()) { walk(path.join(dir, entry.name), relative); continue }
246
+ // `.test.js` alone: skips `.js.map`, `.d.ts`, `.d.ts.map` and any non-test helper module
247
+ if (!entry.name.endsWith('.test.js')) continue
248
+
249
+ found.push(relative)
250
+ }
251
+ }
252
+ walk(root, '')
253
+
254
+ return found.sort()
255
+ }
256
+
257
+ /**
258
+ * Loads every compiled test file and returns the ones that enrolled, in path order.
259
+ *
260
+ * `require` runs each file's module scope. That is safe only because every test file keeps its
261
+ * standalone entrypoint behind `if (require.main === module)` — that guard is load-bearing here.
262
+ */
263
+ export const discover_isolated_suites = (root = isolated_suites_root()): DiscoveredIsolatedSuite[] => {
264
+ const suites: DiscoveredIsolatedSuite[] = []
265
+ const byName: Record<string, string> = {}
266
+ const label = (file: string) => `${path.basename(root)}/${file}`
267
+
268
+ if (!fs.existsSync(root)) {
269
+ // Loud, rather than "0 suites discovered" followed by a green run.
270
+ throw new Error(`No compiled test directory at ${root} — build first (npx tsc -b packages/tsconfig.cjs.json --force)`)
271
+ }
272
+
273
+ for (const file of compiled_test_files(root)) {
274
+ const loaded = require(path.join(root, file))
275
+ const descriptor = loaded?.[ISOLATED_SUITE_EXPORT]
276
+ if (descriptor === undefined || descriptor === null) continue
277
+
278
+ if (typeof descriptor.name !== 'string' || descriptor.name.trim() === '' || typeof descriptor.run !== 'function') {
279
+ throw new Error(
280
+ `${label(file)} exports ${ISOLATED_SUITE_EXPORT}, but it is not { name: string, run: () => Promise<Check[]> }`
281
+ )
282
+ }
283
+ if (byName[descriptor.name]) {
284
+ throw new Error(
285
+ `Two isolated suites are named ${JSON.stringify(descriptor.name)}: ${label(byName[descriptor.name])} and ${label(file)}`
286
+ )
287
+ }
288
+
289
+ byName[descriptor.name] = file
290
+ suites.push({ name: descriptor.name, run: descriptor.run, file })
291
+ }
292
+
293
+ return suites
294
+ }
295
+
296
+ /**
297
+ * Discovers the enrolled suites and runs them through a pool of TEST_PARALLELISM (default 8).
298
+ *
299
+ * One batch at a time, asserting after each batch settles rather than once at the end: a red batch
300
+ * stops the run instead of burning the rest of them. Within a batch the contract is unchanged —
301
+ * run_isolated_in_parallel lets every suite settle and tear down before anything asserts.
302
+ */
303
+ export const run_discovered_isolated_suites = async (
304
+ options?: { root?: string, parallelism?: number },
305
+ ): Promise<DiscoveredIsolatedSuite[]> => {
306
+ const root = options?.root ?? isolated_suites_root()
307
+ const suites = discover_isolated_suites(root)
308
+ const parallelism = options?.parallelism ?? parse_test_parallelism(process.env.TEST_PARALLELISM)
309
+
310
+ console.log(`\nDiscovered ${suites.length} isolated suite(s), running up to ${parallelism} at a time:`)
311
+ for (const s of suites) {
312
+ console.log(` ${s.name} (${path.basename(root)}/${s.file})`)
313
+ }
314
+
315
+ for (let i = 0; i < suites.length; i += parallelism) {
316
+ await run_isolated_in_parallel(suites.slice(i, i + parallelism))
317
+ }
318
+
319
+ return suites
320
+ }
@@ -142,8 +142,6 @@ import { booking_info_idor_tests } from "./api_tests/security/booking-info-idor-
142
142
  import { automated_actions_process_gate_tests } from "./api_tests/security/automated-actions-process-gate.test";
143
143
  import { schema_adminonly_enforcement_tests } from "./api_tests/security/schema-adminonly-enforcement.test";
144
144
  import { startlinktoken_escalation_tests } from "./api_tests/security/startlinktoken-enduser-staff-escalation.test";
145
- import { form_logic_js_execution_checks } from "./api_tests/security/form-logic-js-execution.test";
146
- import { rich_text_answer_sanitized_on_write_checks } from "./api_tests/security/rich-text-answer-sanitized-on-write.test";
147
145
  import { bulk_assignment_tests } from "./api_tests/bulk_assignment.test";
148
146
  import { tickets_bulk_assign_care_team_tests } from "./api_tests/tickets_bulk_assign_care_team.test";
149
147
  import { managed_content_enduser_access_tests } from "./api_tests/managed_content_enduser_access.test";
@@ -164,17 +162,9 @@ import { phone_validation_unit_tests } from "./unit_tests/phone_validation.test"
164
162
  import { openapi_spec_freshness_unit_tests } from "./unit_tests/openapi_spec_freshness.test";
165
163
  import { do_not_send_address_unit_tests } from "./unit_tests/do_not_send_address.test";
166
164
  import { trigger_title_partials_unit_tests } from "./unit_tests/trigger_title_partials.test";
167
- import { trigger_contains_matching_checks } from "./api_tests/trigger_contains_matching.test";
168
- import { organization_signup_checks } from "./api_tests/organization_signup.test";
169
- import { add_to_journey_foreign_enduserid_checks } from "./api_tests/security/add-to-journey-foreign-enduserid.test";
170
- import { tickets_bulk_assign_from_queue_checks } from "./api_tests/tickets_bulk_assign_from_queue.test";
171
- import { enduser_observations_auto_review_checks } from "./api_tests/enduser_observations_auto_review.test";
172
- import { file_link_session_confinement_checks } from "./api_tests/security/file-link-session-confinement.test";
173
- import { appointment_reschedule_ownership_checks } from "./api_tests/security/appointment-reschedule-ownership.test";
174
- import { load_threads_care_team_checks } from "./api_tests/load_threads_care_team.test";
175
- import { run_isolated_in_parallel } from "./setup_isolated";
176
- import { enduser_fields_sync_checks } from "./api_tests/enduser_fields_sync.test";
177
- import { user_logs_restore_deleted_checks } from "./api_tests/user_logs_restore_deleted.test";
165
+ import { isolated_suite_discovery_unit_tests } from "./unit_tests/isolated_suite_discovery.test";
166
+ import { programs_conditional_logic_unit_tests } from "./unit_tests/programs_conditional_logic.test";
167
+ import { run_discovered_isolated_suites } from "./setup_isolated";
178
168
  import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
179
169
  import { organization_settings_duplicates_tests } from "./api_tests/organization_settings_duplicates.test";
180
170
  import { stedi_approval_tests } from "./api_tests/stedi_approval.test";
@@ -6015,6 +6005,37 @@ const search_tests = async () => {
6015
6005
  handleAnyError,
6016
6006
  )
6017
6007
 
6008
+ await async_test(
6009
+ `Search with minimumShouldMatch 1 accepted`,
6010
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: 1 } }),
6011
+ { onResult: es => es.length === 1 && es[0].id === e1.id },
6012
+ )
6013
+ await async_test(
6014
+ `Search with minimumShouldMatch 0 accepted`,
6015
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: 0 } }),
6016
+ { onResult: es => es.length === 1 && es[0].id === e1.id },
6017
+ )
6018
+ await async_test(
6019
+ `Search with minimumShouldMatch and minSearchScore accepted`,
6020
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: 1, minSearchScore: 0.5 } }),
6021
+ { onResult: es => es.length === 1 && es[0].id === e1.id },
6022
+ )
6023
+ await async_test(
6024
+ `Search with negative minimumShouldMatch rejected`,
6025
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: -1 } }),
6026
+ handleAnyError,
6027
+ )
6028
+ await async_test(
6029
+ `Search with non-integer minimumShouldMatch rejected`,
6030
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: 1.5 } }),
6031
+ handleAnyError,
6032
+ )
6033
+ await async_test(
6034
+ `Search with string minimumShouldMatch rejected`,
6035
+ () => sdk.api.endusers.getSome({ search: { query: 'john', minimumShouldMatch: 'one' as any } }),
6036
+ handleAnyError,
6037
+ )
6038
+
6018
6039
  await Promise.all([
6019
6040
  sdk.api.endusers.deleteOne(e1.id),
6020
6041
  sdk.api.endusers.deleteOne(e2.id),
@@ -6840,7 +6861,48 @@ export const databases_tests = async () => {
6840
6861
  'numRecords incremented',
6841
6862
  () => sdk.api.databases.getOne(database.id),
6842
6863
  { onResult: c => c.numRecords === 1 },
6843
- )
6864
+ )
6865
+
6866
+ await async_test(
6867
+ 'load_choices_from_database search with minimumShouldMatch accepted',
6868
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minimumShouldMatch: 1 }),
6869
+ { onResult: ({ choices }) => choices.length === 1 && choices[0].id === databaseRecord.id },
6870
+ )
6871
+ await async_test(
6872
+ 'load_choices_from_database search with minimumShouldMatch and no match returns empty',
6873
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'zzzznonexistent', minimumShouldMatch: 1 }),
6874
+ { onResult: ({ choices }) => choices.length === 0 },
6875
+ )
6876
+ await async_test(
6877
+ 'load_choices_from_database negative minimumShouldMatch rejected',
6878
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minimumShouldMatch: -1 }),
6879
+ handleAnyError,
6880
+ )
6881
+ await async_test(
6882
+ 'load_choices_from_database non-integer minimumShouldMatch rejected',
6883
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minimumShouldMatch: 1.5 }),
6884
+ handleAnyError,
6885
+ )
6886
+ await async_test(
6887
+ 'load_choices_from_database search with minSearchScore accepted',
6888
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minSearchScore: 0.5 }),
6889
+ { onResult: ({ choices }) => choices.length === 1 && choices[0].id === databaseRecord.id },
6890
+ )
6891
+ await async_test(
6892
+ 'load_choices_from_database search with minSearchScore and minimumShouldMatch accepted',
6893
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minSearchScore: 0.5, minimumShouldMatch: 1 }),
6894
+ { onResult: ({ choices }) => choices.length === 1 && choices[0].id === databaseRecord.id },
6895
+ )
6896
+ await async_test(
6897
+ 'load_choices_from_database negative minSearchScore rejected',
6898
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minSearchScore: -1 }),
6899
+ handleAnyError,
6900
+ )
6901
+ await async_test(
6902
+ 'load_choices_from_database string minSearchScore rejected',
6903
+ () => sdk.api.form_fields.load_choices_from_database({ fieldId: database.id, databaseId: database.id, search: 'value', minSearchScore: 'high' as any }),
6904
+ handleAnyError,
6905
+ )
6844
6906
 
6845
6907
  await async_test(
6846
6908
  'Non admin can access correctly',
@@ -14571,6 +14633,46 @@ const inbox_threads_loading_tests = async () => {
14571
14633
  () => sdk.api.inbox_threads.load_threads({ search: 'ab' }),
14572
14634
  { onResult: ({ threads }) => threads.length === 5 }
14573
14635
  )
14636
+ await async_test(
14637
+ 'search with minimumShouldMatch accepted',
14638
+ () => sdk.api.inbox_threads.load_threads({ search: 'Email', minimumShouldMatch: 1 }),
14639
+ { onResult: ({ threads }) => threads.length === 1 && threads[0].type === 'Email' }
14640
+ )
14641
+ await async_test(
14642
+ 'search with minimumShouldMatch and no match returns empty',
14643
+ () => sdk.api.inbox_threads.load_threads({ search: 'zzzznonexistent', minimumShouldMatch: 1 }),
14644
+ { onResult: ({ threads }) => threads.length === 0 }
14645
+ )
14646
+ await async_test(
14647
+ 'search with negative minimumShouldMatch rejected',
14648
+ () => sdk.api.inbox_threads.load_threads({ search: 'Test', minimumShouldMatch: -1 }),
14649
+ handleAnyError,
14650
+ )
14651
+ await async_test(
14652
+ 'search with non-integer minimumShouldMatch rejected',
14653
+ () => sdk.api.inbox_threads.load_threads({ search: 'Test', minimumShouldMatch: 1.5 }),
14654
+ handleAnyError,
14655
+ )
14656
+ await async_test(
14657
+ 'search with minSearchScore accepted',
14658
+ () => sdk.api.inbox_threads.load_threads({ search: 'Email', minSearchScore: 0.5 }),
14659
+ { onResult: ({ threads }) => threads.length === 1 && threads[0].type === 'Email' }
14660
+ )
14661
+ await async_test(
14662
+ 'search with minSearchScore and minimumShouldMatch accepted',
14663
+ () => sdk.api.inbox_threads.load_threads({ search: 'Email', minSearchScore: 0.5, minimumShouldMatch: 1 }),
14664
+ { onResult: ({ threads }) => threads.length === 1 && threads[0].type === 'Email' }
14665
+ )
14666
+ await async_test(
14667
+ 'search with negative minSearchScore rejected',
14668
+ () => sdk.api.inbox_threads.load_threads({ search: 'Test', minSearchScore: -1 }),
14669
+ handleAnyError,
14670
+ )
14671
+ await async_test(
14672
+ 'search with string minSearchScore rejected',
14673
+ () => sdk.api.inbox_threads.load_threads({ search: 'Test', minSearchScore: 'high' as any }),
14674
+ handleAnyError,
14675
+ )
14574
14676
 
14575
14677
  // adding to care team of e2 who belongs to only the phone thread
14576
14678
  await sdk.api.endusers.updateOne(e2.id, { assignedTo: [sdkNonAdmin.userInfo.id] }, { replaceObjectFields: true })
@@ -15402,6 +15504,8 @@ const ip_address_form_tests = async () => {
15402
15504
  await openapi_spec_freshness_unit_tests()
15403
15505
  await do_not_send_address_unit_tests()
15404
15506
  await trigger_title_partials_unit_tests()
15507
+ await isolated_suite_discovery_unit_tests()
15508
+ await programs_conditional_logic_unit_tests()
15405
15509
  await replace_enduser_template_values_tests()
15406
15510
  await safe_json_parse_template_tests()
15407
15511
  await replace_form_field_template_values_tests()
@@ -15409,22 +15513,12 @@ const ip_address_form_tests = async () => {
15409
15513
  await setup_tests(sdk, sdkNonAdmin)
15410
15514
  // Isolated, tenant-provisioned suites run first and CONCURRENTLY: each provisions its own
15411
15515
  // throwaway tenant via /test-support, so they cannot collide with each other or with the
15412
- // shared fixture. Both collect Check[] and tear down before anything asserts — assert calls
15413
- // process.exit, which would otherwise strand the other suite's tenants.
15414
- await run_isolated_in_parallel([
15415
- { name: 'Organization signup', run: organization_signup_checks },
15416
- { name: 'add_to_journey foreign enduserId', run: add_to_journey_foreign_enduserid_checks },
15417
- { name: 'Tickets bulk assign from queue', run: tickets_bulk_assign_from_queue_checks },
15418
- { name: 'Vital auto-review on create', run: enduser_observations_auto_review_checks },
15419
- { name: '$JS in stored conditional logic', run: form_logic_js_execution_checks },
15420
- { name: 'Rich Text answers sanitized on write', run: rich_text_answer_sanitized_on_write_checks },
15421
- { name: 'Shared file link session confinement', run: file_link_session_confinement_checks },
15422
- { name: 'book_appointment reschedule ownership', run: appointment_reschedule_ownership_checks },
15423
- { name: 'Enduser fields sync schema round-trip', run: enduser_fields_sync_checks },
15424
- { name: 'Trigger contains title matching', run: trigger_contains_matching_checks },
15425
- { name: 'user_logs restore_deleted', run: user_logs_restore_deleted_checks },
15426
- { name: 'Load threads care team', run: load_threads_care_team_checks },
15427
- ])
15516
+ // shared fixture. There is no list to maintain here — a suite enrolls itself by exporting
15517
+ // ISOLATED_SUITE from its own file, and the dispatcher discovers it by walking the compiled
15518
+ // api_tests tree. Width is TEST_PARALLELISM (default 8); each batch tears down and asserts
15519
+ // before the next one starts, because assert calls process.exit, which would otherwise strand
15520
+ // a concurrent suite's tenants.
15521
+ await run_discovered_isolated_suites()
15428
15522
  await embeddables_auth_token_tests({ sdk, sdkNonAdmin })
15429
15523
  await proxy_image_ssrf_tests({ sdk, sdkNonAdmin })
15430
15524
  await webhook_outbound_ssrf_tests({ sdk, sdkNonAdmin })