@tellescope/sdk 2.0.0-beta.0 → 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 (303) hide show
  1. package/CLAUDE.md +24 -0
  2. package/env.example +6 -0
  3. package/lib/cjs/sdk.d.ts +376 -3
  4. package/lib/cjs/sdk.d.ts.map +1 -1
  5. package/lib/cjs/sdk.js +1 -0
  6. package/lib/cjs/sdk.js.map +1 -1
  7. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts +22 -0
  8. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts.map +1 -0
  9. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.js +506 -0
  10. package/lib/cjs/tests/api_tests/booking_prevent_upcoming_duplicate.test.js.map +1 -0
  11. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts +3 -1
  12. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -1
  13. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js +3 -1
  14. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js.map +1 -1
  15. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts +3 -1
  16. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -1
  17. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js +3 -1
  18. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -1
  19. package/lib/cjs/tests/api_tests/form_vital_reading.test.d.ts +7 -0
  20. package/lib/cjs/tests/api_tests/form_vital_reading.test.d.ts.map +1 -0
  21. package/lib/cjs/tests/api_tests/form_vital_reading.test.js +461 -0
  22. package/lib/cjs/tests/api_tests/form_vital_reading.test.js.map +1 -0
  23. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.d.ts +26 -0
  24. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.d.ts.map +1 -0
  25. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.js +232 -0
  26. package/lib/cjs/tests/api_tests/journey_statistics_cache.test.js.map +1 -0
  27. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.d.ts +34 -0
  28. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.d.ts.map +1 -0
  29. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.js +243 -0
  30. package/lib/cjs/tests/api_tests/journey_step_manual_count.test.js.map +1 -0
  31. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts +3 -1
  32. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -1
  33. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js +3 -1
  34. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js.map +1 -1
  35. package/lib/cjs/tests/api_tests/mdi_case_offerings.test.d.ts.map +1 -1
  36. package/lib/cjs/tests/api_tests/mdi_case_offerings.test.js +139 -9
  37. package/lib/cjs/tests/api_tests/mdi_case_offerings.test.js.map +1 -1
  38. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +3 -1
  39. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -1
  40. package/lib/cjs/tests/api_tests/organization_signup.test.js +3 -1
  41. package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -1
  42. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.d.ts +7 -0
  43. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.d.ts.map +1 -0
  44. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.js +571 -0
  45. package/lib/cjs/tests/api_tests/phone_embed_leave_message.test.js.map +1 -0
  46. package/lib/cjs/tests/api_tests/programs.test.d.ts +3 -1
  47. package/lib/cjs/tests/api_tests/programs.test.d.ts.map +1 -1
  48. package/lib/cjs/tests/api_tests/programs.test.js +73 -17
  49. package/lib/cjs/tests/api_tests/programs.test.js.map +1 -1
  50. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.d.ts +7 -0
  51. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.d.ts.map +1 -0
  52. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.js +733 -0
  53. package/lib/cjs/tests/api_tests/public_form_case_insensitive_match.test.js.map +1 -0
  54. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +3 -1
  55. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -1
  56. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +58 -48
  57. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -1
  58. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +3 -1
  59. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -1
  60. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js +3 -1
  61. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -1
  62. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts +3 -1
  63. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -1
  64. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js +3 -1
  65. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -1
  66. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts +3 -1
  67. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -1
  68. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js +3 -1
  69. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -1
  70. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -1
  71. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +424 -39
  72. package/lib/cjs/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -1
  73. package/lib/cjs/tests/api_tests/security/register_business_id_injection.test.js +2 -2
  74. package/lib/cjs/tests/api_tests/security/register_business_id_injection.test.js.map +1 -1
  75. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +3 -1
  76. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -1
  77. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +3 -1
  78. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -1
  79. package/lib/cjs/tests/api_tests/session_for_join_link.test.d.ts +10 -0
  80. package/lib/cjs/tests/api_tests/session_for_join_link.test.d.ts.map +1 -0
  81. package/lib/cjs/tests/api_tests/session_for_join_link.test.js +147 -0
  82. package/lib/cjs/tests/api_tests/session_for_join_link.test.js.map +1 -0
  83. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
  84. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +191 -88
  85. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
  86. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.d.ts +17 -0
  87. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.d.ts.map +1 -0
  88. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.js +219 -0
  89. package/lib/cjs/tests/api_tests/stripe_checkout_items.test.js.map +1 -0
  90. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.d.ts +5 -0
  91. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.d.ts.map +1 -0
  92. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.js +396 -0
  93. package/lib/cjs/tests/api_tests/stripe_multiple_customer_ids.test.js.map +1 -0
  94. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +3 -1
  95. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -1
  96. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +3 -1
  97. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -1
  98. package/lib/cjs/tests/api_tests/totp_mfa.test.d.ts.map +1 -1
  99. package/lib/cjs/tests/api_tests/totp_mfa.test.js +37 -31
  100. package/lib/cjs/tests/api_tests/totp_mfa.test.js.map +1 -1
  101. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts +3 -1
  102. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -1
  103. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js +3 -1
  104. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js.map +1 -1
  105. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts +3 -1
  106. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -1
  107. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js +3 -1
  108. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -1
  109. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts +3 -1
  110. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -1
  111. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js +3 -1
  112. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js.map +1 -1
  113. package/lib/cjs/tests/api_tests/vitl_push.test.d.ts +7 -0
  114. package/lib/cjs/tests/api_tests/vitl_push.test.d.ts.map +1 -0
  115. package/lib/cjs/tests/api_tests/vitl_push.test.js +341 -0
  116. package/lib/cjs/tests/api_tests/vitl_push.test.js.map +1 -0
  117. package/lib/cjs/tests/setup_isolated.d.ts +48 -0
  118. package/lib/cjs/tests/setup_isolated.d.ts.map +1 -1
  119. package/lib/cjs/tests/setup_isolated.js +119 -1
  120. package/lib/cjs/tests/setup_isolated.js.map +1 -1
  121. package/lib/cjs/tests/tests.d.ts.map +1 -1
  122. package/lib/cjs/tests/tests.js +378 -282
  123. package/lib/cjs/tests/tests.js.map +1 -1
  124. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.d.ts +2 -0
  125. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.d.ts.map +1 -0
  126. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.js +201 -0
  127. package/lib/cjs/tests/unit_tests/isolated_suite_discovery.test.js.map +1 -0
  128. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.d.ts +3 -0
  129. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.d.ts.map +1 -0
  130. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.js +120 -0
  131. package/lib/cjs/tests/unit_tests/programs_conditional_logic.test.js.map +1 -0
  132. package/lib/cjs/tests/webhooks_tests.js +44 -34
  133. package/lib/cjs/tests/webhooks_tests.js.map +1 -1
  134. package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  135. package/lib/esm/sdk.d.ts +376 -3
  136. package/lib/esm/sdk.d.ts.map +1 -1
  137. package/lib/esm/sdk.js +1 -0
  138. package/lib/esm/sdk.js.map +1 -1
  139. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts +22 -0
  140. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.d.ts.map +1 -0
  141. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.js +501 -0
  142. package/lib/esm/tests/api_tests/booking_prevent_upcoming_duplicate.test.js.map +1 -0
  143. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts +3 -1
  144. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -1
  145. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js +2 -0
  146. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js.map +1 -1
  147. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts +3 -1
  148. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -1
  149. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js +2 -0
  150. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -1
  151. package/lib/esm/tests/api_tests/form_vital_reading.test.d.ts +7 -0
  152. package/lib/esm/tests/api_tests/form_vital_reading.test.d.ts.map +1 -0
  153. package/lib/esm/tests/api_tests/form_vital_reading.test.js +456 -0
  154. package/lib/esm/tests/api_tests/form_vital_reading.test.js.map +1 -0
  155. package/lib/esm/tests/api_tests/journey_statistics_cache.test.d.ts +26 -0
  156. package/lib/esm/tests/api_tests/journey_statistics_cache.test.d.ts.map +1 -0
  157. package/lib/esm/tests/api_tests/journey_statistics_cache.test.js +227 -0
  158. package/lib/esm/tests/api_tests/journey_statistics_cache.test.js.map +1 -0
  159. package/lib/esm/tests/api_tests/journey_step_manual_count.test.d.ts +34 -0
  160. package/lib/esm/tests/api_tests/journey_step_manual_count.test.d.ts.map +1 -0
  161. package/lib/esm/tests/api_tests/journey_step_manual_count.test.js +238 -0
  162. package/lib/esm/tests/api_tests/journey_step_manual_count.test.js.map +1 -0
  163. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts +3 -1
  164. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -1
  165. package/lib/esm/tests/api_tests/load_threads_care_team.test.js +2 -0
  166. package/lib/esm/tests/api_tests/load_threads_care_team.test.js.map +1 -1
  167. package/lib/esm/tests/api_tests/mdi_case_offerings.test.d.ts.map +1 -1
  168. package/lib/esm/tests/api_tests/mdi_case_offerings.test.js +139 -9
  169. package/lib/esm/tests/api_tests/mdi_case_offerings.test.js.map +1 -1
  170. package/lib/esm/tests/api_tests/organization_signup.test.d.ts +3 -1
  171. package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -1
  172. package/lib/esm/tests/api_tests/organization_signup.test.js +2 -0
  173. package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -1
  174. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.d.ts +7 -0
  175. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.d.ts.map +1 -0
  176. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.js +566 -0
  177. package/lib/esm/tests/api_tests/phone_embed_leave_message.test.js.map +1 -0
  178. package/lib/esm/tests/api_tests/programs.test.d.ts +3 -1
  179. package/lib/esm/tests/api_tests/programs.test.d.ts.map +1 -1
  180. package/lib/esm/tests/api_tests/programs.test.js +72 -16
  181. package/lib/esm/tests/api_tests/programs.test.js.map +1 -1
  182. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.d.ts +7 -0
  183. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.d.ts.map +1 -0
  184. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.js +728 -0
  185. package/lib/esm/tests/api_tests/public_form_case_insensitive_match.test.js.map +1 -0
  186. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +3 -1
  187. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -1
  188. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +57 -47
  189. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -1
  190. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +3 -1
  191. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -1
  192. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js +2 -0
  193. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -1
  194. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts +3 -1
  195. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -1
  196. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js +2 -0
  197. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -1
  198. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts +3 -1
  199. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -1
  200. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js +2 -0
  201. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -1
  202. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.d.ts.map +1 -1
  203. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js +424 -39
  204. package/lib/esm/tests/api_tests/security/formsort_webhook_auth_field_lock.test.js.map +1 -1
  205. package/lib/esm/tests/api_tests/security/register_business_id_injection.test.js +2 -2
  206. package/lib/esm/tests/api_tests/security/register_business_id_injection.test.js.map +1 -1
  207. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +3 -1
  208. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -1
  209. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +2 -0
  210. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -1
  211. package/lib/esm/tests/api_tests/session_for_join_link.test.d.ts +10 -0
  212. package/lib/esm/tests/api_tests/session_for_join_link.test.d.ts.map +1 -0
  213. package/lib/esm/tests/api_tests/session_for_join_link.test.js +142 -0
  214. package/lib/esm/tests/api_tests/session_for_join_link.test.js.map +1 -0
  215. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
  216. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +191 -88
  217. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
  218. package/lib/esm/tests/api_tests/stripe_checkout_items.test.d.ts +17 -0
  219. package/lib/esm/tests/api_tests/stripe_checkout_items.test.d.ts.map +1 -0
  220. package/lib/esm/tests/api_tests/stripe_checkout_items.test.js +214 -0
  221. package/lib/esm/tests/api_tests/stripe_checkout_items.test.js.map +1 -0
  222. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.d.ts +5 -0
  223. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.d.ts.map +1 -0
  224. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.js +388 -0
  225. package/lib/esm/tests/api_tests/stripe_multiple_customer_ids.test.js.map +1 -0
  226. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +3 -1
  227. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -1
  228. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +2 -0
  229. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -1
  230. package/lib/esm/tests/api_tests/totp_mfa.test.d.ts.map +1 -1
  231. package/lib/esm/tests/api_tests/totp_mfa.test.js +37 -31
  232. package/lib/esm/tests/api_tests/totp_mfa.test.js.map +1 -1
  233. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts +3 -1
  234. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -1
  235. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js +2 -0
  236. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js.map +1 -1
  237. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts +3 -1
  238. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -1
  239. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js +2 -0
  240. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -1
  241. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts +3 -1
  242. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -1
  243. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js +2 -0
  244. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js.map +1 -1
  245. package/lib/esm/tests/api_tests/vitl_push.test.d.ts +7 -0
  246. package/lib/esm/tests/api_tests/vitl_push.test.d.ts.map +1 -0
  247. package/lib/esm/tests/api_tests/vitl_push.test.js +333 -0
  248. package/lib/esm/tests/api_tests/vitl_push.test.js.map +1 -0
  249. package/lib/esm/tests/setup_isolated.d.ts +48 -0
  250. package/lib/esm/tests/setup_isolated.d.ts.map +1 -1
  251. package/lib/esm/tests/setup_isolated.js +114 -0
  252. package/lib/esm/tests/setup_isolated.js.map +1 -1
  253. package/lib/esm/tests/tests.d.ts.map +1 -1
  254. package/lib/esm/tests/tests.js +379 -283
  255. package/lib/esm/tests/tests.js.map +1 -1
  256. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.d.ts +2 -0
  257. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.d.ts.map +1 -0
  258. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.js +174 -0
  259. package/lib/esm/tests/unit_tests/isolated_suite_discovery.test.js.map +1 -0
  260. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.d.ts +3 -0
  261. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.d.ts.map +1 -0
  262. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.js +117 -0
  263. package/lib/esm/tests/unit_tests/programs_conditional_logic.test.js.map +1 -0
  264. package/lib/esm/tests/webhooks_tests.js +44 -34
  265. package/lib/esm/tests/webhooks_tests.js.map +1 -1
  266. package/lib/tsconfig.tsbuildinfo +1 -1
  267. package/package.json +12 -11
  268. package/src/sdk.ts +9 -2
  269. package/src/tests/api_tests/booking_prevent_upcoming_duplicate.test.ts +541 -0
  270. package/src/tests/api_tests/enduser_fields_sync.test.ts +154 -0
  271. package/src/tests/api_tests/enduser_observations_auto_review.test.ts +4 -0
  272. package/src/tests/api_tests/form_vital_reading.test.ts +393 -0
  273. package/src/tests/api_tests/journey_statistics_cache.test.ts +237 -0
  274. package/src/tests/api_tests/journey_step_manual_count.test.ts +220 -0
  275. package/src/tests/api_tests/load_threads_care_team.test.ts +214 -0
  276. package/src/tests/api_tests/mdi_case_offerings.test.ts +138 -0
  277. package/src/tests/api_tests/organization_signup.test.ts +4 -0
  278. package/src/tests/api_tests/phone_embed_leave_message.test.ts +541 -0
  279. package/src/tests/api_tests/programs.test.ts +351 -0
  280. package/src/tests/api_tests/public_form_case_insensitive_match.test.ts +632 -0
  281. package/src/tests/api_tests/security/add-to-journey-foreign-enduserid.test.ts +37 -26
  282. package/src/tests/api_tests/security/appointment-reschedule-ownership.test.ts +436 -0
  283. package/src/tests/api_tests/security/file-link-session-confinement.test.ts +4 -0
  284. package/src/tests/api_tests/security/form-logic-js-execution.test.ts +4 -0
  285. package/src/tests/api_tests/security/formsort_webhook_auth_field_lock.test.ts +225 -0
  286. package/src/tests/api_tests/security/register_business_id_injection.test.ts +2 -2
  287. package/src/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.ts +4 -0
  288. package/src/tests/api_tests/session_for_join_link.test.ts +99 -0
  289. package/src/tests/api_tests/set_fields_order_templates.test.ts +94 -9
  290. package/src/tests/api_tests/stripe_checkout_items.test.ts +186 -0
  291. package/src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts +4 -0
  292. package/src/tests/api_tests/totp_mfa.test.ts +10 -5
  293. package/src/tests/api_tests/trigger_contains_matching.test.ts +349 -0
  294. package/src/tests/api_tests/user_logs_restore_deleted.test.ts +419 -0
  295. package/src/tests/api_tests/vital_review_trigger_action.test.ts +455 -0
  296. package/src/tests/api_tests/vitl_push.test.ts +323 -0
  297. package/src/tests/setup_isolated.ts +140 -0
  298. package/src/tests/tests.ts +126 -20
  299. package/src/tests/unit_tests/isolated_suite_discovery.test.ts +178 -0
  300. package/src/tests/unit_tests/programs_conditional_logic.test.ts +189 -0
  301. package/src/tests/unit_tests/trigger_title_partials.test.ts +174 -0
  302. package/src/tests/webhooks_tests.ts +6 -1
  303. package/test_generated.pdf +0 -0
@@ -0,0 +1,186 @@
1
+ require('source-map-support').install();
2
+
3
+ import {
4
+ assert_checks,
5
+ Check,
6
+ setup_isolated_tenant,
7
+ IsolatedSuite,
8
+ } from "../setup_isolated"
9
+ import { authenticate_enduser_via_token } from "../setup"
10
+ import { EnduserSession } from "../../sdk"
11
+
12
+ const host = process.env.API_URL || 'http://localhost:8080' as const
13
+
14
+ const RAND = () => Math.random().toString(36).slice(2, 10)
15
+
16
+ const ENDUSER_RAW_IDS_MESSAGE = "stripePriceId and subscriptionPriceId are not supported for enduser sessions"
17
+ const QUANTITY_MESSAGE = "quantity must be a whole number of at least 1"
18
+ const EXACTLY_ONE_ID_MESSAGE = "Each items entry must specify exactly one of productId, stripePriceId, or subscriptionPriceId"
19
+ // what the handler throws once a request is past every items guard and no Stripe account is
20
+ // configured - the marker for "this cart was accepted, it only ran out of Stripe"
21
+ const NO_STRIPE_MESSAGE = "Stripe Connection not found"
22
+
23
+ /**
24
+ * The guards on the `items` parameter of products/prepare-stripe-checkout that run BEFORE any Stripe
25
+ * integration is resolved, so they are reachable without a Stripe account: the enduser rejection of
26
+ * raw Stripe price ids, the productIds/items exclusivity, the per-entry shape rules and the quantity
27
+ * validator.
28
+ *
29
+ * The quantity guards themselves (the cap of 99, the two "greater than 1" rejections) sit on the
30
+ * Stripe Custom branch and are pinned instead by the unit suite in
31
+ * packages/private/api/api/integrations/payments/stripe_line_items.test.ts, which needs no database
32
+ * and no Stripe account. Anything that creates or inspects a real Checkout session is manual QA.
33
+ */
34
+ export const stripe_checkout_items_checks = async (): Promise<Check[]> => {
35
+ const checks: Check[] = []
36
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
37
+
38
+ let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
39
+
40
+ try {
41
+ tenant = await setup_isolated_tenant({
42
+ name: `stripe items ${RAND()}`,
43
+ users: [{ isAdmin: true }],
44
+ apiKeys: 0,
45
+ })
46
+ const { sdk, businessId } = tenant
47
+
48
+ const product = await sdk.api.products.createOne({
49
+ title: `Checkout Item Product ${RAND()}`,
50
+ cost: { amount: 500, currency: 'USD' },
51
+ processor: 'Stripe',
52
+ })
53
+
54
+ const enduser = await sdk.api.endusers.createOne({ email: `items_${RAND()}@test.tellescope.com` })
55
+ const enduserSDK = new EnduserSession({ host, businessId })
56
+ await authenticate_enduser_via_token(sdk, enduserSDK, { id: enduser.id })
57
+
58
+ // returns the error message for a rejected request, or '' when the request succeeded
59
+ const errorFor = async (session: typeof sdk | typeof enduserSDK, args: any): Promise<string> => {
60
+ try {
61
+ await (session.api.products.prepare_stripe_checkout as any)(args)
62
+ return ''
63
+ } catch (err: any) {
64
+ return typeof err === 'string' ? err : (err?.message ?? JSON.stringify(err))
65
+ }
66
+ }
67
+
68
+ // ---- raw Stripe price ids are staff / API-key only ----
69
+ const enduserRawPrice = await errorFor(enduserSDK, { items: [{ stripePriceId: 'price_raw', quantity: 1 }] })
70
+ record(
71
+ 'an enduser naming a raw stripePriceId is rejected',
72
+ enduserRawPrice === ENDUSER_RAW_IDS_MESSAGE,
73
+ `expected "${ENDUSER_RAW_IDS_MESSAGE}", got "${enduserRawPrice}"`,
74
+ )
75
+
76
+ const enduserRawSubscription = await errorFor(enduserSDK, { items: [{ subscriptionPriceId: 'price_sub', quantity: 1 }] })
77
+ record(
78
+ 'an enduser naming a raw subscriptionPriceId is rejected',
79
+ enduserRawSubscription === ENDUSER_RAW_IDS_MESSAGE,
80
+ `expected "${ENDUSER_RAW_IDS_MESSAGE}", got "${enduserRawSubscription}"`,
81
+ )
82
+
83
+ // the rejection is about raw Stripe ids, not about items: a patient can still buy a Tellescope
84
+ // product at a quantity, and only runs out of road at the missing Stripe account
85
+ const enduserProduct = await errorFor(enduserSDK, { items: [{ productId: product.id, quantity: 2 }] })
86
+ record(
87
+ 'an enduser naming a Tellescope productId is allowed through the guard',
88
+ enduserProduct === NO_STRIPE_MESSAGE,
89
+ `expected "${NO_STRIPE_MESSAGE}", got "${enduserProduct}"`,
90
+ )
91
+
92
+ // the same raw price from a staff session passes the guard
93
+ const userRawPrice = await errorFor(sdk, { items: [{ stripePriceId: 'price_raw', quantity: 1 }] })
94
+ record(
95
+ 'a user session naming a raw stripePriceId is allowed through the guard',
96
+ userRawPrice === NO_STRIPE_MESSAGE,
97
+ `expected "${NO_STRIPE_MESSAGE}", got "${userRawPrice}"`,
98
+ )
99
+
100
+ // ---- productIds and items are mutually exclusive ----
101
+ const both = await errorFor(sdk, { productIds: [product.id], items: [{ productId: product.id, quantity: 1 }] })
102
+ record(
103
+ 'passing both productIds and items is rejected',
104
+ both === "Pass either productIds or items, not both",
105
+ `expected the "not both" message, got "${both}"`,
106
+ )
107
+
108
+ const neither = await errorFor(sdk, {})
109
+ record(
110
+ 'passing neither productIds nor items is rejected',
111
+ neither === "One of productIds or items is required",
112
+ `expected the "one of" message, got "${neither}"`,
113
+ )
114
+
115
+ // ---- per-entry shape ----
116
+ const noId = await errorFor(sdk, { items: [{ quantity: 1 }] })
117
+ record(
118
+ 'an items entry naming no id is rejected',
119
+ noId === EXACTLY_ONE_ID_MESSAGE,
120
+ `expected the "exactly one" message, got "${noId}"`,
121
+ )
122
+
123
+ const twoIds = await errorFor(sdk, { items: [{ productId: product.id, stripePriceId: 'price_raw', quantity: 1 }] })
124
+ record(
125
+ 'an items entry naming two ids is rejected',
126
+ twoIds === EXACTLY_ONE_ID_MESSAGE,
127
+ `expected the "exactly one" message, got "${twoIds}"`,
128
+ )
129
+
130
+ // unlike productIds, which silently drops ids it cannot find, items rejects the whole cart
131
+ const unknownId = '60398b0231a295e64f084fd9'
132
+ const unknown = await errorFor(sdk, { items: [{ productId: unknownId, quantity: 1 }] })
133
+ record(
134
+ 'an items entry naming an unknown productId is rejected, naming the id',
135
+ unknown.includes(unknownId) && unknown.includes('No product found'),
136
+ `expected a "No product found for productId ${unknownId}", got "${unknown}"`,
137
+ )
138
+
139
+ // ---- quantity must be a whole number of at least 1 ----
140
+ // quantity: 0 is the one that nearly got away: build_validator returns 0 early for any isNumber
141
+ // field, so the schema validator cannot reject it and the guard in resolve_checkout_items is
142
+ // the only thing between a zero quantity and a Stripe line item
143
+ for (const quantity of [0, -1, 1.5]) {
144
+ const message = await errorFor(sdk, { items: [{ productId: product.id, quantity }] })
145
+ record(
146
+ `quantity ${quantity} is rejected`,
147
+ message.includes(QUANTITY_MESSAGE),
148
+ `expected a message containing "${QUANTITY_MESSAGE}", got "${message}"`,
149
+ )
150
+ }
151
+
152
+ // a productIds-only request still reaches the Stripe lookup exactly as before
153
+ const productIdsOnly = await errorFor(sdk, { productIds: [product.id] })
154
+ record(
155
+ 'a productIds-only request is unaffected',
156
+ productIdsOnly === NO_STRIPE_MESSAGE,
157
+ `expected "${NO_STRIPE_MESSAGE}", got "${productIdsOnly}"`,
158
+ )
159
+ } finally {
160
+ // teardown before anything asserts — assert calls process.exit, which skips pending finallys
161
+ if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
162
+ }
163
+
164
+ return checks
165
+ }
166
+
167
+ export const stripe_checkout_items_tests = async () => {
168
+ assert_checks('prepare_stripe_checkout items guards', await stripe_checkout_items_checks())
169
+ }
170
+
171
+ /** Enrolls this suite in the auto-discovered parallel pool — see run_discovered_isolated_suites. */
172
+ export const ISOLATED_SUITE: IsolatedSuite = { name: 'prepare_stripe_checkout items guards', run: stripe_checkout_items_checks }
173
+
174
+ // Allow running this test file independently
175
+ if (require.main === module) {
176
+ console.log(`🌐 Using API URL: ${host}`)
177
+ stripe_checkout_items_tests()
178
+ .then(() => {
179
+ console.log("✅ stripe_checkout_items test suite completed successfully")
180
+ process.exit(0)
181
+ })
182
+ .catch((error) => {
183
+ console.error("❌ stripe_checkout_items test suite failed:", error)
184
+ process.exit(1)
185
+ })
186
+ }
@@ -5,6 +5,7 @@ import {
5
5
  assert_checks,
6
6
  Check,
7
7
  setup_isolated_tenant,
8
+ IsolatedSuite,
8
9
  } from "../setup_isolated"
9
10
 
10
11
  const host = process.env.API_URL || 'http://localhost:8080' as const
@@ -264,6 +265,9 @@ export const tickets_bulk_assign_from_queue_tests = async () => {
264
265
  assert_checks('Tickets bulk assign from queue', await tickets_bulk_assign_from_queue_checks())
265
266
  }
266
267
 
268
+ /** Enrolls this suite in the auto-discovered parallel pool — see run_discovered_isolated_suites. */
269
+ export const ISOLATED_SUITE: IsolatedSuite = { name: 'Tickets bulk assign from queue', run: tickets_bulk_assign_from_queue_checks }
270
+
267
271
  // Allow running this test file independently
268
272
  if (require.main === module) {
269
273
  console.log(`🌐 Using API URL: ${host}`)
@@ -302,11 +302,16 @@ export const totp_mfa_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkN
302
302
  // the already-configured email method must keep working (no lockout)
303
303
  await set_allowed_mfa_methods(['authenticator'])
304
304
 
305
- await async_test(
306
- 'E5. grace: configured email challenge still works after restriction',
307
- () => sdkNonAdmin.api.users.generate_MFA_challenge({ method: 'email' }),
308
- { shouldError: false, onResult: () => true }
309
- )
305
+ // a successful challenge really emails the code, so only send it to a do-not-send address
306
+ if (NON_ADMIN_EMAIL.startsWith('dontsend') && NON_ADMIN_EMAIL.endsWith('@tellescope.com')) {
307
+ await async_test(
308
+ 'E5. grace: configured email challenge still works after restriction',
309
+ () => sdkNonAdmin.api.users.generate_MFA_challenge({ method: 'email' }),
310
+ { shouldError: false, onResult: () => true }
311
+ )
312
+ } else {
313
+ console.log(' E5 skipped: NON_ADMIN_EMAIL is not a dontsend address, a challenge would send a real email')
314
+ }
310
315
 
311
316
  // cleanup E: clear restriction + disable email
312
317
  await clear_allowed_mfa_methods()
@@ -0,0 +1,349 @@
1
+ require('source-map-support').install();
2
+
3
+ import {
4
+ assert_checks,
5
+ Check,
6
+ setup_isolated_tenant,
7
+ IsolatedSuite,
8
+ } from "../setup_isolated"
9
+
10
+ const host = process.env.API_URL || 'http://localhost:8080' as const
11
+
12
+ const RAND = () => Math.random().toString(36).slice(2, 10)
13
+
14
+ /**
15
+ * End-to-end coverage for "contains" title matching on the `Problem Created` and `Order Created`
16
+ * automation triggers — the optional `titlePartials` (case sensitive) and `titlePartialsIgnoreCase`
17
+ * lists that sit beside the existing exact `titles` list.
18
+ *
19
+ * Three backend call sites implement this and they must agree:
20
+ * - handle_problems_create (enduser_problems.ts — single AND bulk problem creates)
21
+ * - handle_order_created (automation_triggers.ts — orders created ONE at a time)
22
+ * - handle_orders_create bulk branch (enduser_orders.ts — orders created via createSome, >1)
23
+ *
24
+ * The order paths diverge structurally (`created.length === 1` takes an entirely different branch),
25
+ * so every order assertion here is made twice: once through `createOne` and once through a
26
+ * `createSome` of more than one record. That divergence is the main risk in this change.
27
+ *
28
+ * Own tenant: automation triggers are business-wide, so a shared tenant would let these triggers tag
29
+ * other concurrently running suites' endusers and vice versa. Never calls sdk.reset_db().
30
+ *
31
+ * Trigger actions are fire-and-forget, so positives poll. Negatives wait for a catch-all sentinel
32
+ * trigger (one with no title filter at all, which must fire for every record) to land first, then
33
+ * settle before reading — otherwise "the tag is absent" would just mean "the tag has not arrived
34
+ * yet".
35
+ */
36
+
37
+ const poll_for = async <T,>(get: () => Promise<T>, done: (v: T) => boolean, attempts=24): Promise<T> => {
38
+ let value = await get()
39
+ for (let i = 0; i < attempts && !done(value); i++) {
40
+ await new Promise(resolve => setTimeout(resolve, 250))
41
+ value = await get()
42
+ }
43
+ return value
44
+ }
45
+
46
+ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
47
+
48
+ export const trigger_contains_matching_checks = async (): Promise<Check[]> => {
49
+ const checks: Check[] = []
50
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
51
+
52
+ let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
53
+
54
+ try {
55
+ tenant = await setup_isolated_tenant({
56
+ name: `ContainsTriggers ${RAND()}`,
57
+ users: [{ isAdmin: true }],
58
+ apiKeys: 0,
59
+ })
60
+ const { sdk } = tenant
61
+
62
+ /* ============================ triggers ============================ */
63
+
64
+ const trigger = (
65
+ title: string,
66
+ event: any,
67
+ tag: string,
68
+ ) => sdk.api.automation_triggers.createOne({
69
+ title,
70
+ status: 'Active',
71
+ event,
72
+ action: { type: 'Add Tags', info: { tags: [tag] } },
73
+ })
74
+
75
+ await Promise.all([
76
+ // ---- Order Created ----
77
+ // fires for every order — the sentinel that tells a negative check "the handler has run"
78
+ trigger('Order any', { type: 'Order Created', info: {} }, 'order-any'),
79
+ trigger('Order contains CS', { type: 'Order Created', info: { titlePartials: ['Semaglutide'] } }, 'order-cs'),
80
+ trigger('Order contains CI', { type: 'Order Created', info: { titlePartialsIgnoreCase: ['semaglutide'] } }, 'order-ci'),
81
+ // exact titles AND a contains fragment — both must pass
82
+ trigger(
83
+ 'Order exact AND contains',
84
+ { type: 'Order Created', info: { titles: ['Semaglutide 0.25mg'], titlePartials: ['Sema'] } },
85
+ 'order-and',
86
+ ),
87
+ // exact-only, unchanged prior behaviour
88
+ trigger('Order exact only', { type: 'Order Created', info: { titles: ['Semaglutide 0.25mg'] } }, 'order-exact'),
89
+
90
+ // ---- Problem Created ----
91
+ trigger('Problem any', { type: 'Problem Created', info: {} }, 'problem-any'),
92
+ trigger('Problem contains CS', { type: 'Problem Created', info: { titlePartials: ['Endometriosis'] } }, 'problem-cs'),
93
+ trigger('Problem contains CI', { type: 'Problem Created', info: { titlePartialsIgnoreCase: ['endometriosis'] } }, 'problem-ci'),
94
+ trigger(
95
+ 'Problem exact AND contains',
96
+ { type: 'Problem Created', info: { titles: ['Endometriosis, unspecified'], titlePartials: ['Endo'] } },
97
+ 'problem-and',
98
+ ),
99
+ trigger('Problem exact only', { type: 'Problem Created', info: { titles: ['Diabetes'] } }, 'problem-exact'),
100
+ // entries are trimmed before matching
101
+ trigger('Problem contains padded', { type: 'Problem Created', info: { titlePartials: [' Endo '] } }, 'problem-padded'),
102
+ // a blank sibling entry is dropped, not treated as "matches everything"
103
+ trigger(
104
+ 'Problem contains with blank entry',
105
+ { type: 'Problem Created', info: { titlePartials: ['Endometriosis', ' '] } },
106
+ 'problem-blankentry',
107
+ ),
108
+ ])
109
+
110
+ /* ============================ helpers ============================ */
111
+
112
+ const new_enduser = (fname: string) => sdk.api.endusers.createOne({ fname, lname: 'Contains' })
113
+
114
+ /** enduser_orders requires externalId/source/status; none of them affect title matching. */
115
+ const order_fields = (enduserId: string, title: string) => ({
116
+ enduserId,
117
+ title,
118
+ externalId: `contains-${RAND()}`,
119
+ source: 'ContainsQA',
120
+ status: 'created',
121
+ })
122
+
123
+ /** Polls until the sentinel tag has landed, then settles so absent tags mean absent. */
124
+ const tags_after_settling = async (enduserId: string, sentinel: string) => {
125
+ await poll_for(
126
+ () => sdk.api.endusers.getOne(enduserId),
127
+ e => !!e.tags?.includes(sentinel),
128
+ )
129
+ await wait(1500)
130
+ const e = await sdk.api.endusers.getOne(enduserId)
131
+ return e.tags ?? []
132
+ }
133
+
134
+ const expect_tags = async (
135
+ label: string,
136
+ enduserId: string,
137
+ sentinel: string,
138
+ expected: string[],
139
+ unexpected: string[],
140
+ ) => {
141
+ // give the positives a chance to arrive before the sentinel settle window closes
142
+ await poll_for(
143
+ () => sdk.api.endusers.getOne(enduserId),
144
+ e => expected.every(t => e.tags?.includes(t)),
145
+ )
146
+ const tags = await tags_after_settling(enduserId, sentinel)
147
+ const missing = expected.filter(t => !tags.includes(t))
148
+ const present = unexpected.filter(t => tags.includes(t))
149
+ record(
150
+ label,
151
+ missing.length === 0 && present.length === 0,
152
+ `tags=${JSON.stringify(tags)}`
153
+ + (missing.length ? ` MISSING=${JSON.stringify(missing)}` : '')
154
+ + (present.length ? ` UNEXPECTED=${JSON.stringify(present)}` : ''),
155
+ )
156
+ }
157
+
158
+ /* ============================ orders: single-create path ============================ */
159
+
160
+ const orderSingleMatch = await new_enduser('OrderSingleMatch')
161
+ await sdk.api.enduser_orders.createOne(order_fields(orderSingleMatch.id, 'Semaglutide 0.25mg'))
162
+ await expect_tags(
163
+ 'A1: single-created order matching both contains lists and the exact title fires all four triggers',
164
+ orderSingleMatch.id, 'order-any',
165
+ ['order-any', 'order-cs', 'order-ci', 'order-and', 'order-exact'],
166
+ [],
167
+ )
168
+
169
+ const orderSingleCase = await new_enduser('OrderSingleCase')
170
+ await sdk.api.enduser_orders.createOne(order_fields(orderSingleCase.id, 'SEMAGLUTIDE injection'))
171
+ await expect_tags(
172
+ 'A2: single-created order in a different case fires only the case-insensitive contains trigger',
173
+ orderSingleCase.id, 'order-any',
174
+ ['order-any', 'order-ci'],
175
+ ['order-cs', 'order-and', 'order-exact'],
176
+ )
177
+
178
+ const orderSingleMiss = await new_enduser('OrderSingleMiss')
179
+ await sdk.api.enduser_orders.createOne(order_fields(orderSingleMiss.id, 'Tirzepatide 5mg'))
180
+ await expect_tags(
181
+ 'A3: single-created order matching no contains entry fires only the unfiltered trigger',
182
+ orderSingleMiss.id, 'order-any',
183
+ ['order-any'],
184
+ ['order-cs', 'order-ci', 'order-and', 'order-exact'],
185
+ )
186
+
187
+ const orderSingleAndMiss = await new_enduser('OrderSingleAndMiss')
188
+ // contains 'Sema' but is not exactly 'Semaglutide 0.25mg' — the lists AND, they do not OR
189
+ await sdk.api.enduser_orders.createOne(order_fields(orderSingleAndMiss.id, 'Semaglutide 1mg'))
190
+ await expect_tags(
191
+ 'A4: exact+contains trigger does NOT fire when only the contains half matches (single create)',
192
+ orderSingleAndMiss.id, 'order-any',
193
+ ['order-any', 'order-cs', 'order-ci'],
194
+ ['order-and', 'order-exact'],
195
+ )
196
+
197
+ /* ============================ orders: bulk-create path ============================ */
198
+ /* createSome with more than one record takes handle_orders_create's bulk branch, which is a
199
+ * separate implementation of the same filtering. Same four assertions, same expectations. */
200
+
201
+ const [bulkMatch, bulkCase, bulkMiss, bulkAndMiss] = await Promise.all([
202
+ new_enduser('OrderBulkMatch'),
203
+ new_enduser('OrderBulkCase'),
204
+ new_enduser('OrderBulkMiss'),
205
+ new_enduser('OrderBulkAndMiss'),
206
+ ])
207
+
208
+ await sdk.api.enduser_orders.createSome([
209
+ order_fields(bulkMatch.id, 'Semaglutide 0.25mg'),
210
+ order_fields(bulkCase.id, 'SEMAGLUTIDE injection'),
211
+ order_fields(bulkMiss.id, 'Tirzepatide 5mg'),
212
+ order_fields(bulkAndMiss.id, 'Semaglutide 1mg'),
213
+ ])
214
+
215
+ await expect_tags(
216
+ 'B1: bulk-created order matching both contains lists and the exact title fires all four triggers',
217
+ bulkMatch.id, 'order-any',
218
+ ['order-any', 'order-cs', 'order-ci', 'order-and', 'order-exact'],
219
+ [],
220
+ )
221
+ await expect_tags(
222
+ 'B2: bulk-created order in a different case fires only the case-insensitive contains trigger',
223
+ bulkCase.id, 'order-any',
224
+ ['order-any', 'order-ci'],
225
+ ['order-cs', 'order-and', 'order-exact'],
226
+ )
227
+ await expect_tags(
228
+ 'B3: bulk-created order matching no contains entry fires only the unfiltered trigger',
229
+ bulkMiss.id, 'order-any',
230
+ ['order-any'],
231
+ ['order-cs', 'order-ci', 'order-and', 'order-exact'],
232
+ )
233
+ await expect_tags(
234
+ 'B4: exact+contains trigger does NOT fire when only the contains half matches (bulk create)',
235
+ bulkAndMiss.id, 'order-any',
236
+ ['order-any', 'order-cs', 'order-ci'],
237
+ ['order-and', 'order-exact'],
238
+ )
239
+
240
+ /* ============================ problems ============================ */
241
+
242
+ const problemMatch = await new_enduser('ProblemMatch')
243
+ await sdk.api.enduser_problems.createOne({ enduserId: problemMatch.id, title: 'Endometriosis, unspecified' })
244
+ await expect_tags(
245
+ 'C1: problem whose title contains the case-sensitive fragment fires the contains triggers',
246
+ problemMatch.id, 'problem-any',
247
+ ['problem-any', 'problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-blankentry'],
248
+ ['problem-exact'],
249
+ )
250
+
251
+ const problemCase = await new_enduser('ProblemCase')
252
+ await sdk.api.enduser_problems.createOne({ enduserId: problemCase.id, title: 'Chronic endometriosis pain' })
253
+ await expect_tags(
254
+ 'C2: a lowercase title fires the case-insensitive trigger but not the case-sensitive one',
255
+ problemCase.id, 'problem-any',
256
+ ['problem-any', 'problem-ci'],
257
+ ['problem-cs', 'problem-and', 'problem-blankentry', 'problem-exact'],
258
+ )
259
+
260
+ const problemAndMiss = await new_enduser('ProblemAndMiss')
261
+ // contains 'Endo' (so the padded trigger fires) but is not the exact title the AND trigger needs
262
+ await sdk.api.enduser_problems.createOne({ enduserId: problemAndMiss.id, title: 'Endometrial hyperplasia' })
263
+ await expect_tags(
264
+ 'C3: exact+contains problem trigger does NOT fire on a title that only satisfies the contains half',
265
+ problemAndMiss.id, 'problem-any',
266
+ ['problem-any', 'problem-padded'],
267
+ ['problem-and', 'problem-cs', 'problem-ci', 'problem-blankentry', 'problem-exact'],
268
+ )
269
+
270
+ const problemBlank = await new_enduser('ProblemBlank')
271
+ await sdk.api.enduser_problems.createOne({ enduserId: problemBlank.id, title: 'Common cold' })
272
+ await expect_tags(
273
+ 'C4: a blank entry beside a real one does not make the trigger fire on an unrelated title',
274
+ problemBlank.id, 'problem-any',
275
+ ['problem-any'],
276
+ ['problem-blankentry', 'problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-exact'],
277
+ )
278
+
279
+ /* ---- problems created in bulk (the EHR-sync shape) take the same handler with created.length > 1 ---- */
280
+
281
+ const [problemBulkMatch, problemBulkMiss] = await Promise.all([
282
+ new_enduser('ProblemBulkMatch'),
283
+ new_enduser('ProblemBulkMiss'),
284
+ ])
285
+ await sdk.api.enduser_problems.createSome([
286
+ { enduserId: problemBulkMatch.id, title: 'Endometriosis, unspecified' },
287
+ { enduserId: problemBulkMiss.id, title: 'Common cold' },
288
+ ])
289
+ await expect_tags(
290
+ 'D1: bulk-created problem matching the contains lists fires the same triggers as a single create',
291
+ problemBulkMatch.id, 'problem-any',
292
+ ['problem-any', 'problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-blankentry'],
293
+ ['problem-exact'],
294
+ )
295
+ await expect_tags(
296
+ 'D2: an unrelated problem in the same bulk create fires only the unfiltered trigger',
297
+ problemBulkMiss.id, 'problem-any',
298
+ ['problem-any'],
299
+ ['problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-blankentry'],
300
+ )
301
+
302
+ /* ============================ regression: exact matching unchanged ============================ */
303
+
304
+ const exactHit = await new_enduser('ExactHit')
305
+ await sdk.api.enduser_problems.createOne({ enduserId: exactHit.id, title: 'Diabetes' })
306
+ await expect_tags(
307
+ 'E1: an exact-titles-only trigger still fires on an exactly matching title',
308
+ exactHit.id, 'problem-any',
309
+ ['problem-any', 'problem-exact'],
310
+ ['problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-blankentry'],
311
+ )
312
+
313
+ const exactMiss = await new_enduser('ExactMiss')
314
+ await sdk.api.enduser_problems.createOne({ enduserId: exactMiss.id, title: 'Diabetes mellitus type 2' })
315
+ await expect_tags(
316
+ 'E2: an exact-titles-only trigger still does NOT fire on a superstring of its title',
317
+ exactMiss.id, 'problem-any',
318
+ ['problem-any'],
319
+ ['problem-exact', 'problem-cs', 'problem-ci', 'problem-and', 'problem-padded', 'problem-blankentry'],
320
+ )
321
+ } finally {
322
+ // Teardown BEFORE anything asserts — assert calls process.exit, which would strand this tenant.
323
+ if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
324
+ }
325
+
326
+ return checks
327
+ }
328
+
329
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
330
+ export const trigger_contains_matching_tests = async () => {
331
+ assert_checks('Trigger contains title matching', await trigger_contains_matching_checks())
332
+ }
333
+
334
+ /** Enrolls this suite in the auto-discovered parallel pool — see run_discovered_isolated_suites. */
335
+ export const ISOLATED_SUITE: IsolatedSuite = { name: 'Trigger contains title matching', run: trigger_contains_matching_checks }
336
+
337
+ // Allow running this test file independently
338
+ if (require.main === module) {
339
+ console.log(`🌐 Using API URL: ${host}`)
340
+ trigger_contains_matching_tests()
341
+ .then(() => {
342
+ console.log("✅ Trigger contains matching test suite completed successfully")
343
+ process.exit(0)
344
+ })
345
+ .catch((error) => {
346
+ console.error("❌ Trigger contains matching test suite failed:", error)
347
+ process.exit(1)
348
+ })
349
+ }