@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,455 @@
1
+ require('source-map-support').install();
2
+
3
+ import { SYSTEM_REVIEWER_ID } from "@tellescope/constants"
4
+ import {
5
+ assert_checks,
6
+ Check,
7
+ setup_isolated_tenant,
8
+ IsolatedSuite,
9
+ } from "../setup_isolated"
10
+
11
+ const host = process.env.API_URL || 'http://localhost:8080' as const
12
+
13
+ const RAND = () => Math.random().toString(36).slice(2, 10)
14
+
15
+ /**
16
+ * Coverage for the "Review Vital Reading" automation trigger action, and — because the same change
17
+ * carved that action out of two shared dedupe mechanisms — for the dedupe mechanisms themselves.
18
+ *
19
+ * The action marks the reading in `journeyContext.observationId` as reviewed
20
+ * (`reviewedAt` + `reviewedBy = SYSTEM_REVIEWER_ID`), so a trigger's own gating (programs, enduser
21
+ * conditions, active hours) can decide what auto-reviews. Only the Vital Update event supplies an
22
+ * observationId, so the pairing is enforced by a schema relationship constraint.
23
+ *
24
+ * To be useful it has to run for EVERY matching reading, which means opting out of both things that
25
+ * normally collapse repeat fires:
26
+ * 1. the 60s `throttled_events` window, one fire per (trigger, enduser) — automation_triggers.ts
27
+ * 2. the per-batch `enduserIdTriggerTracker`, one fire per (trigger, enduser) per create batch,
28
+ * which a blood pressure reading trips because it creates SBP + DBP together — vitals.ts
29
+ *
30
+ * Both carve-outs are qualified by action type, and both live on code paths every other trigger
31
+ * shares. Checks G-I are the regression half of this suite: they pin that the ordinary behavior is
32
+ * unchanged for other actions and other events. Nothing else in the repo asserts trigger
33
+ * throttling — every other trigger suite is written to avoid it (one enduser per case) rather than
34
+ * to test it — so if these go, the platform-wide throttle has no coverage at all.
35
+ *
36
+ * This suite provisions its own throwaway tenant. That is not optional: `vital_configurations` are
37
+ * business-wide (handle_vital_create reads them with `enduserId: { $exists: false }` across the
38
+ * businessId), so on the shared fixture these configurations would classify other concurrently
39
+ * running suites' readings and theirs would bleed into these checks. It never calls `sdk.reset_db()`,
40
+ * which clears global throttle namespaces shared with other suites — which would be especially
41
+ * destructive here, where the throttle is the thing under test.
42
+ *
43
+ * Checks are separated by vital unit rather than by name, because a configuration applies to every
44
+ * reading of its unit across the tenant.
45
+ *
46
+ * Synchronization. Classification and trigger actions are asynchronous create side effects, so every
47
+ * read polls.
48
+ * - Positive checks poll directly on the condition they assert, which is sound on its own.
49
+ * - Negative checks ("this did NOT fire") need a barrier, or they would pass by reading too early.
50
+ * Two are used. Where a control trigger fires on the same reading, it is created AFTER the
51
+ * trigger under test so it runs last within `triggersForClassification` (the same ordering
52
+ * programs.test.ts relies on). Where the control would itself be throttled, the barrier is a
53
+ * Review Vital Reading trigger instead — it is throttle-exempt by construction, so its effect
54
+ * landing proves the later reading was processed.
55
+ */
56
+
57
+ const poll_for = async <T,>(get: () => Promise<T>, done: (v: T) => boolean, attempts=20): Promise<T> => {
58
+ let value = await get()
59
+ for (let i = 0; i < attempts && !done(value); i++) {
60
+ await new Promise(resolve => setTimeout(resolve, 250))
61
+ value = await get()
62
+ }
63
+ return value
64
+ }
65
+
66
+ export const vital_review_trigger_action_checks = async (): Promise<Check[]> => {
67
+ const checks: Check[] = []
68
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
69
+
70
+ let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
71
+
72
+ try {
73
+ tenant = await setup_isolated_tenant({
74
+ name: `VitalReviewAction ${RAND()}`,
75
+ users: [{ isAdmin: true }],
76
+ apiKeys: 0,
77
+ })
78
+ const { sdk } = tenant
79
+
80
+ const high_configuration = (unit: string, over: number) => sdk.api.vital_configurations.createOne({
81
+ title: `Review action ${unit} ${RAND()}`,
82
+ unit,
83
+ ranges: [{ classification: 'High', comparison: { type: 'Greater Than', value: over } }],
84
+ })
85
+
86
+ const new_enduser = (label: string) => sdk.api.endusers.createOne({ fname: 'Review', lname: label })
87
+
88
+ const add_reading = (enduserId: string, unit: string, value: number, extra: object = {}) => (
89
+ sdk.api.enduser_observations.createOne({
90
+ category: 'vital-signs',
91
+ status: 'final',
92
+ enduserId,
93
+ measurement: { unit, value },
94
+ timestamp: new Date(),
95
+ ...extra,
96
+ })
97
+ )
98
+
99
+ const is_system_reviewed = (o: { reviewedAt?: Date, reviewedBy?: string }) => (
100
+ !!o.reviewedAt && o.reviewedBy === SYSTEM_REVIEWER_ID
101
+ )
102
+
103
+ /* ================= A, D, E: the action marks the reading in context ================= */
104
+
105
+ // one configuration, two ranges, so a reading can match the trigger's classification or miss it
106
+ const glucoseConfiguration = await sdk.api.vital_configurations.createOne({
107
+ title: `Review action glucose ${RAND()}`,
108
+ unit: 'mg/dL',
109
+ ranges: [
110
+ { classification: 'High', comparison: { type: 'Greater Than', value: 200 } },
111
+ { classification: 'Low', comparison: { type: 'Less Than', value: 50 } },
112
+ ],
113
+ })
114
+
115
+ // created FIRST, so the control trigger below runs after it on the same reading
116
+ await sdk.api.automation_triggers.createOne({
117
+ title: `Review High glucose ${RAND()}`,
118
+ status: 'Active',
119
+ event: {
120
+ type: 'Vital Update',
121
+ info: { configurationIds: [glucoseConfiguration.id], classifications: ['High'] },
122
+ },
123
+ action: { type: 'Review Vital Reading', info: {} },
124
+ })
125
+ // control/barrier: matches both classifications, so its tag marks "this reading was processed"
126
+ await sdk.api.automation_triggers.createOne({
127
+ title: `Glucose processed marker ${RAND()}`,
128
+ status: 'Active',
129
+ event: {
130
+ type: 'Vital Update',
131
+ info: { configurationIds: [glucoseConfiguration.id], classifications: ['High', 'Low'] },
132
+ },
133
+ action: { type: 'Add Tags', info: { tags: ['glucose-processed'] } },
134
+ })
135
+
136
+ const matchingEnduser = await new_enduser('Matching')
137
+ const matchingReading = await add_reading(matchingEnduser.id, 'mg/dL', 250) // High
138
+ const reviewedMatching = await poll_for(
139
+ () => sdk.api.enduser_observations.getOne(matchingReading.id),
140
+ is_system_reviewed,
141
+ )
142
+ record(
143
+ 'A: Review Vital Reading marks the reading that fired the trigger as system-reviewed',
144
+ is_system_reviewed(reviewedMatching),
145
+ `reviewedAt=${JSON.stringify(reviewedMatching.reviewedAt)} reviewedBy=${JSON.stringify(reviewedMatching.reviewedBy)}`,
146
+ )
147
+
148
+ // a reading classified Low never matches this trigger, so it must stay in the unreviewed queue
149
+ const otherClassEnduser = await new_enduser('OtherClass')
150
+ const otherClassReading = await add_reading(otherClassEnduser.id, 'mg/dL', 10) // Low
151
+ await poll_for(
152
+ () => sdk.api.endusers.getOne(otherClassEnduser.id),
153
+ e => !!e.tags?.includes('glucose-processed'),
154
+ )
155
+ const afterOtherClass = await sdk.api.enduser_observations.getOne(otherClassReading.id)
156
+ record(
157
+ 'D: a reading whose classification the trigger does not match is left unreviewed',
158
+ !afterOtherClass.reviewedAt,
159
+ `reviewedAt=${JSON.stringify(afterOtherClass.reviewedAt)}`
160
+ + ` (barrier tag seen, so the reading was processed)`,
161
+ )
162
+
163
+ // an already-reviewed reading must not be re-stamped — the $exists: false guard on the update
164
+ const PRE_REVIEWED_AT = new Date('2020-01-02T03:04:05.000Z')
165
+ const preReviewedEnduser = await new_enduser('PreReviewed')
166
+ const preReviewedReading = await add_reading(
167
+ preReviewedEnduser.id, 'mg/dL', 250, { reviewedAt: PRE_REVIEWED_AT },
168
+ )
169
+ await poll_for(
170
+ () => sdk.api.endusers.getOne(preReviewedEnduser.id),
171
+ e => !!e.tags?.includes('glucose-processed'),
172
+ )
173
+ const afterPreReviewed = await sdk.api.enduser_observations.getOne(preReviewedReading.id)
174
+ record(
175
+ 'E: an already-reviewed reading is not re-stamped',
176
+ !!afterPreReviewed.reviewedAt
177
+ && new Date(afterPreReviewed.reviewedAt).getTime() === PRE_REVIEWED_AT.getTime()
178
+ && afterPreReviewed.reviewedBy !== SYSTEM_REVIEWER_ID,
179
+ `reviewedAt=${JSON.stringify(afterPreReviewed.reviewedAt)} (want ${PRE_REVIEWED_AT.toISOString()})`
180
+ + ` reviewedBy=${JSON.stringify(afterPreReviewed.reviewedBy)}`,
181
+ )
182
+
183
+ /* ============ B: every reading of a multi-component batch (the tracker carve-out) ============ */
184
+
185
+ const sbpConfiguration = await high_configuration('SBP', 200)
186
+ const dbpConfiguration = await high_configuration('DBP', 120)
187
+
188
+ await sdk.api.automation_triggers.createOne({
189
+ title: `Review High BP ${RAND()}`,
190
+ status: 'Active',
191
+ event: {
192
+ type: 'Vital Update',
193
+ info: {
194
+ configurationIds: [sbpConfiguration.id, dbpConfiguration.id],
195
+ classifications: ['High'],
196
+ },
197
+ },
198
+ action: { type: 'Review Vital Reading', info: {} },
199
+ })
200
+
201
+ // one blood pressure reading = two observations in ONE create batch for ONE enduser, which is
202
+ // exactly what the per-batch tracker would otherwise collapse to a single fire
203
+ const bpEnduser = await new_enduser('BloodPressure')
204
+ const bpReadings = await sdk.api.enduser_observations.createSome([
205
+ {
206
+ category: 'vital-signs', status: 'final', enduserId: bpEnduser.id,
207
+ measurement: { unit: 'SBP', value: 250 }, timestamp: new Date(),
208
+ },
209
+ {
210
+ category: 'vital-signs', status: 'final', enduserId: bpEnduser.id,
211
+ measurement: { unit: 'DBP', value: 150 }, timestamp: new Date(),
212
+ },
213
+ ])
214
+ const bpIds = bpReadings.created.map(o => o.id)
215
+ const bpAfter = await poll_for(
216
+ () => Promise.all(bpIds.map(id => sdk.api.enduser_observations.getOne(id))),
217
+ os => os.every(is_system_reviewed),
218
+ )
219
+ record(
220
+ 'B: both observations of a two-component reading created in one batch are reviewed',
221
+ bpAfter.length === 2 && bpAfter.every(is_system_reviewed),
222
+ bpAfter.map(o => `${o.measurement.unit}:${o.reviewedBy ?? 'unreviewed'}`).join(' '),
223
+ )
224
+
225
+ /* ============ C: readings seconds apart (the throttle carve-out) ============ */
226
+
227
+ const bpmConfiguration = await high_configuration('BPM', 150)
228
+ await sdk.api.automation_triggers.createOne({
229
+ title: `Review High BPM ${RAND()}`,
230
+ status: 'Active',
231
+ event: {
232
+ type: 'Vital Update',
233
+ info: { configurationIds: [bpmConfiguration.id], classifications: ['High'] },
234
+ },
235
+ action: { type: 'Review Vital Reading', info: {} },
236
+ })
237
+
238
+ // Same enduser, same trigger, well inside the 60s window that would normally swallow the second.
239
+ // The second reading is only created once the first has been reviewed: the throttle record is
240
+ // written at the END of the fire it belongs to, so two readings posted back to back race it and
241
+ // both get through even with the carve-out removed. Waiting makes the second fire genuinely
242
+ // throttle-eligible, which is what gives this check its teeth.
243
+ const burstEnduser = await new_enduser('Burst')
244
+ const burstFirst = await add_reading(burstEnduser.id, 'BPM', 180)
245
+ await poll_for(
246
+ () => sdk.api.enduser_observations.getOne(burstFirst.id),
247
+ is_system_reviewed,
248
+ )
249
+ const burstSecond = await add_reading(burstEnduser.id, 'BPM', 190)
250
+ const burstSecondAfter = await poll_for(
251
+ () => sdk.api.enduser_observations.getOne(burstSecond.id),
252
+ is_system_reviewed,
253
+ )
254
+ record(
255
+ 'C: a second reading inside the 60s window is still reviewed (throttle-exempt action)',
256
+ is_system_reviewed(burstSecondAfter),
257
+ `second reading reviewedBy=${JSON.stringify(burstSecondAfter.reviewedBy)}`
258
+ + ` (first reading was already reviewed, so the throttle record existed)`,
259
+ )
260
+
261
+ /* ================= F: the action is rejected on events with no reading ================= */
262
+
263
+ let createError = ''
264
+ try {
265
+ await sdk.api.automation_triggers.createOne({
266
+ title: `Review action wrong event ${RAND()}`,
267
+ status: 'Active',
268
+ event: { type: 'Field Equals', info: { field: '0', value: 'anything' } },
269
+ action: { type: 'Review Vital Reading', info: {} },
270
+ })
271
+ } catch (err: any) { createError = err?.message ?? String(err) }
272
+ record(
273
+ 'F: creating a Review Vital Reading trigger on a non-Vital Update event is rejected',
274
+ createError.includes('Review Vital Reading') && createError.includes('Vital Update'),
275
+ `error=${JSON.stringify(createError) || '(none — the trigger was ACCEPTED)'}`,
276
+ )
277
+
278
+ const validTrigger = await sdk.api.automation_triggers.createOne({
279
+ title: `Review action valid, to be broken ${RAND()}`,
280
+ status: 'Active',
281
+ event: {
282
+ type: 'Vital Update',
283
+ info: { configurationIds: [bpmConfiguration.id], classifications: ['Critical High'] },
284
+ },
285
+ action: { type: 'Review Vital Reading', info: {} },
286
+ })
287
+ let updateError = ''
288
+ try {
289
+ await sdk.api.automation_triggers.updateOne(validTrigger.id, {
290
+ event: { type: 'Field Equals', info: { field: '0', value: 'anything' } },
291
+ })
292
+ } catch (err: any) { updateError = err?.message ?? String(err) }
293
+ const afterUpdate = await sdk.api.automation_triggers.getOne(validTrigger.id)
294
+ record(
295
+ 'F2: switching an existing Review Vital Reading trigger to a non-Vital Update event is rejected',
296
+ updateError.includes('Review Vital Reading') && afterUpdate.event.type === 'Vital Update',
297
+ `error=${JSON.stringify(updateError) || '(none — the update was ACCEPTED)'}`
298
+ + ` event=${afterUpdate.event.type}`,
299
+ )
300
+
301
+ /* ======= G-I: the carve-outs must not leak to other actions or other events ======= */
302
+
303
+ // G. the 60s throttle still applies to a Vital Update trigger with an ordinary action. Same
304
+ // event and timing as check C — the only difference is the action type.
305
+ const o2Configuration = await high_configuration('O2 Sat%', 90)
306
+ await sdk.api.automation_triggers.createOne({
307
+ title: `O2 alert ${RAND()}`,
308
+ status: 'Active',
309
+ event: {
310
+ type: 'Vital Update',
311
+ info: { configurationIds: [o2Configuration.id], classifications: ['High'] },
312
+ },
313
+ action: { type: 'Add Tags', info: { tags: ['o2-alert'] } },
314
+ })
315
+ // barrier for the second reading: throttle-exempt by construction, so its effect landing proves
316
+ // the reading was processed and the missing tag is a throttle, not a race
317
+ await sdk.api.automation_triggers.createOne({
318
+ title: `O2 review barrier ${RAND()}`,
319
+ status: 'Active',
320
+ event: {
321
+ type: 'Vital Update',
322
+ info: { configurationIds: [o2Configuration.id], classifications: ['High'] },
323
+ },
324
+ action: { type: 'Review Vital Reading', info: {} },
325
+ })
326
+
327
+ const o2Enduser = await new_enduser('Oxygen')
328
+ await add_reading(o2Enduser.id, 'O2 Sat%', 95)
329
+ await poll_for(
330
+ () => sdk.api.endusers.getOne(o2Enduser.id),
331
+ e => !!e.tags?.includes('o2-alert'),
332
+ )
333
+ await sdk.api.endusers.updateOne(o2Enduser.id, { tags: [] }, { replaceObjectFields: true })
334
+
335
+ const o2Second = await add_reading(o2Enduser.id, 'O2 Sat%', 97)
336
+ await poll_for(
337
+ () => sdk.api.enduser_observations.getOne(o2Second.id),
338
+ is_system_reviewed,
339
+ )
340
+ const o2After = await sdk.api.endusers.getOne(o2Enduser.id)
341
+ record(
342
+ 'G: the 60s throttle still applies to a Vital Update trigger with an ordinary action',
343
+ !o2After.tags?.includes('o2-alert'),
344
+ `tags=${JSON.stringify(o2After.tags)}`
345
+ + ` (the second reading was reviewed, so it was processed)`,
346
+ )
347
+
348
+ // H. the throttle still applies to an entirely different event type. The carve-out lives in
349
+ // handle_actions_for_triggers_inner, which every trigger event shares.
350
+ await sdk.api.automation_triggers.createOne({
351
+ title: `Field equals throttle ${RAND()}`,
352
+ status: 'Active',
353
+ event: { type: 'Field Equals', info: { field: '0', value: 'throttle-test' } },
354
+ action: { type: 'Add Tags', info: { tags: ['fe-fired'] } },
355
+ })
356
+
357
+ const feEnduser = await new_enduser('FieldEquals')
358
+ await sdk.api.endusers.updateOne(feEnduser.id, { fields: { '0': 'throttle-test' } })
359
+ await poll_for(
360
+ () => sdk.api.endusers.getOne(feEnduser.id),
361
+ e => !!e.tags?.includes('fe-fired'),
362
+ )
363
+ await sdk.api.endusers.updateOne(feEnduser.id, { tags: [] }, { replaceObjectFields: true })
364
+
365
+ await sdk.api.endusers.updateOne(feEnduser.id, { fields: { '0': 'something-else' } })
366
+ await sdk.api.endusers.updateOne(feEnduser.id, { fields: { '0': 'throttle-test' } })
367
+ // barrier: the same trigger on a DIFFERENT enduser has no throttle record, so its tag landing
368
+ // shows the trigger is processing updates made after the throttled one
369
+ const feBarrierEnduser = await new_enduser('FieldEqualsBarrier')
370
+ await sdk.api.endusers.updateOne(feBarrierEnduser.id, { fields: { '0': 'throttle-test' } })
371
+ await poll_for(
372
+ () => sdk.api.endusers.getOne(feBarrierEnduser.id),
373
+ e => !!e.tags?.includes('fe-fired'),
374
+ )
375
+ const feAfter = await sdk.api.endusers.getOne(feEnduser.id)
376
+ record(
377
+ 'H: the 60s throttle still applies to a non-vital event type (Field Equals)',
378
+ !feAfter.tags?.includes('fe-fired'),
379
+ `tags=${JSON.stringify(feAfter.tags)}`
380
+ + ` (the same trigger fired for another enduser afterwards, so it was processing)`,
381
+ )
382
+
383
+ // I. the per-batch tracker is unchanged for other actions: one fire per enduser per batch, even
384
+ // though the batch carries two classifying readings. Create User Notifications is used because
385
+ // it is countable, unlike Add Tags.
386
+ const NOTIFICATION_TYPE = `review-tracker-${RAND()}`
387
+ const a1cConfiguration = await high_configuration('A1C', 9)
388
+ const flowConfiguration = await high_configuration('L/min', 300)
389
+ await sdk.api.automation_triggers.createOne({
390
+ title: `Batch notification ${RAND()}`,
391
+ status: 'Active',
392
+ event: {
393
+ type: 'Vital Update',
394
+ info: {
395
+ configurationIds: [a1cConfiguration.id, flowConfiguration.id],
396
+ classifications: ['High'],
397
+ },
398
+ },
399
+ action: {
400
+ type: 'Create User Notifications',
401
+ info: { message: 'Two readings arrived together', notificationType: NOTIFICATION_TYPE },
402
+ },
403
+ })
404
+
405
+ const batchEnduser = await new_enduser('BatchNotify')
406
+ await sdk.api.enduser_observations.createSome([
407
+ {
408
+ category: 'vital-signs', status: 'final', enduserId: batchEnduser.id,
409
+ measurement: { unit: 'A1C', value: 12 }, timestamp: new Date(),
410
+ },
411
+ {
412
+ category: 'vital-signs', status: 'final', enduserId: batchEnduser.id,
413
+ measurement: { unit: 'L/min', value: 400 }, timestamp: new Date(),
414
+ },
415
+ ])
416
+ const load_notifications = () => sdk.api.user_notifications.getSome({
417
+ filter: { type: NOTIFICATION_TYPE },
418
+ })
419
+ await poll_for(load_notifications, n => n.length > 0)
420
+ // keep polling past the first arrival, so a second fire would have time to show up
421
+ const notifications = await poll_for(load_notifications, n => n.length > 1, 8)
422
+ record(
423
+ 'I: the per-batch tracker still collapses other actions to one fire per batch',
424
+ notifications.length === 1,
425
+ `notifications=${notifications.length} (want 1, for a batch of 2 classifying readings)`,
426
+ )
427
+ } finally {
428
+ // Teardown BEFORE anything asserts — assert calls process.exit, which would strand this tenant.
429
+ if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
430
+ }
431
+
432
+ return checks
433
+ }
434
+
435
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
436
+ export const vital_review_trigger_action_tests = async () => {
437
+ assert_checks('Review Vital Reading trigger action', await vital_review_trigger_action_checks())
438
+ }
439
+
440
+ /** Enrolls this suite in the auto-discovered parallel pool — see run_discovered_isolated_suites. */
441
+ export const ISOLATED_SUITE: IsolatedSuite = { name: 'Review Vital Reading trigger action', run: vital_review_trigger_action_checks }
442
+
443
+ // Allow running this test file independently
444
+ if (require.main === module) {
445
+ console.log(`🌐 Using API URL: ${host}`)
446
+ vital_review_trigger_action_tests()
447
+ .then(() => {
448
+ console.log("✅ Review Vital Reading trigger action suite completed successfully")
449
+ process.exit(0)
450
+ })
451
+ .catch((error) => {
452
+ console.error("❌ Review Vital Reading trigger action suite failed:", error)
453
+ process.exit(1)
454
+ })
455
+ }