@tellescope/sdk 1.256.16 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CLAUDE.md +5 -0
  2. package/env.example +39 -0
  3. package/lib/cjs/enduser.d.ts +1 -0
  4. package/lib/cjs/enduser.d.ts.map +1 -1
  5. package/lib/cjs/sdk.d.ts +3 -1
  6. package/lib/cjs/sdk.d.ts.map +1 -1
  7. package/lib/cjs/sdk.js +1 -0
  8. package/lib/cjs/sdk.js.map +1 -1
  9. package/lib/cjs/session.d.ts +1 -0
  10. package/lib/cjs/session.d.ts.map +1 -1
  11. package/lib/cjs/tests/api_tests/active_patient_count.test.js +1 -1
  12. package/lib/cjs/tests/api_tests/active_patient_count.test.js.map +1 -1
  13. package/lib/cjs/tests/api_tests/appointment_rescheduled_trigger.test.js +1 -1
  14. package/lib/cjs/tests/api_tests/appointment_rescheduled_trigger.test.js.map +1 -1
  15. package/lib/cjs/tests/api_tests/calendar_event_limits.test.js +5 -5
  16. package/lib/cjs/tests/api_tests/calendar_event_limits.test.js.map +1 -1
  17. package/lib/cjs/tests/api_tests/calendar_event_references_update.test.js +1 -1
  18. package/lib/cjs/tests/api_tests/calendar_event_references_update.test.js.map +1 -1
  19. package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js +1 -1
  20. package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
  21. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts +21 -0
  22. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -0
  23. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js +215 -0
  24. package/lib/cjs/tests/api_tests/enduser_fields_sync.test.js.map +1 -0
  25. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js +6 -6
  26. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -1
  27. package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js +3 -3
  28. package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js.map +1 -1
  29. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts +32 -0
  30. package/lib/cjs/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -0
  31. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js +257 -0
  32. package/lib/cjs/tests/api_tests/load_threads_care_team.test.js.map +1 -0
  33. package/lib/cjs/tests/api_tests/managed_content_enduser_access.test.js +1 -1
  34. package/lib/cjs/tests/api_tests/managed_content_enduser_access.test.js.map +1 -1
  35. package/lib/cjs/tests/api_tests/managed_content_file_access.test.js +1 -1
  36. package/lib/cjs/tests/api_tests/managed_content_file_access.test.js.map +1 -1
  37. package/lib/cjs/tests/api_tests/mdi_webhooks.test.d.ts.map +1 -1
  38. package/lib/cjs/tests/api_tests/mdi_webhooks.test.js +21 -8
  39. package/lib/cjs/tests/api_tests/mdi_webhooks.test.js.map +1 -1
  40. package/lib/cjs/tests/api_tests/nutrition_tracking.test.js +1 -1
  41. package/lib/cjs/tests/api_tests/nutrition_tracking.test.js.map +1 -1
  42. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +1 -1
  43. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  44. package/lib/cjs/tests/api_tests/programs.test.d.ts +5 -0
  45. package/lib/cjs/tests/api_tests/programs.test.d.ts.map +1 -0
  46. package/lib/cjs/tests/api_tests/programs.test.js +370 -0
  47. package/lib/cjs/tests/api_tests/programs.test.js.map +1 -0
  48. package/lib/cjs/tests/api_tests/programs_trigger_filters.test.d.ts +5 -0
  49. package/lib/cjs/tests/api_tests/programs_trigger_filters.test.d.ts.map +1 -0
  50. package/lib/cjs/tests/api_tests/programs_trigger_filters.test.js +295 -0
  51. package/lib/cjs/tests/api_tests/programs_trigger_filters.test.js.map +1 -0
  52. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js +1 -1
  53. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -1
  54. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +42 -0
  55. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -0
  56. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js +484 -0
  57. package/lib/cjs/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -0
  58. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js +1 -1
  59. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -1
  60. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js +1 -1
  61. package/lib/cjs/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js.map +1 -1
  62. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js +1 -1
  63. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js.map +1 -1
  64. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js +1 -1
  65. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -1
  66. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js +2 -2
  67. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -1
  68. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +32 -0
  69. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -0
  70. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +297 -0
  71. package/lib/cjs/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -0
  72. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -1
  73. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +4 -1
  74. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -1
  75. package/lib/cjs/tests/api_tests/templated_message_date_fields.test.d.ts +3 -0
  76. package/lib/cjs/tests/api_tests/templated_message_date_fields.test.d.ts.map +1 -0
  77. package/lib/cjs/tests/api_tests/templated_message_date_fields.test.js +202 -0
  78. package/lib/cjs/tests/api_tests/templated_message_date_fields.test.js.map +1 -0
  79. package/lib/cjs/tests/api_tests/templated_message_link_in_href.test.d.ts +3 -0
  80. package/lib/cjs/tests/api_tests/templated_message_link_in_href.test.d.ts.map +1 -0
  81. package/lib/cjs/tests/api_tests/templated_message_link_in_href.test.js +233 -0
  82. package/lib/cjs/tests/api_tests/templated_message_link_in_href.test.js.map +1 -0
  83. package/lib/cjs/tests/api_tests/templated_message_links.test.d.ts +3 -0
  84. package/lib/cjs/tests/api_tests/templated_message_links.test.d.ts.map +1 -0
  85. package/lib/cjs/tests/api_tests/templated_message_links.test.js +235 -0
  86. package/lib/cjs/tests/api_tests/templated_message_links.test.js.map +1 -0
  87. package/lib/cjs/tests/api_tests/time_tracks.test.js +1 -1
  88. package/lib/cjs/tests/api_tests/time_tracks.test.js.map +1 -1
  89. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts +5 -0
  90. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -0
  91. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js +395 -0
  92. package/lib/cjs/tests/api_tests/trigger_contains_matching.test.js.map +1 -0
  93. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts +5 -0
  94. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -0
  95. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js +471 -0
  96. package/lib/cjs/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -0
  97. package/lib/cjs/tests/api_tests/user_portal_settings.test.js +1 -1
  98. package/lib/cjs/tests/api_tests/user_portal_settings.test.js.map +1 -1
  99. package/lib/cjs/tests/api_tests/validation_behavior_locks.test.d.ts +2 -0
  100. package/lib/cjs/tests/api_tests/validation_behavior_locks.test.d.ts.map +1 -0
  101. package/lib/cjs/tests/api_tests/validation_behavior_locks.test.js +233 -0
  102. package/lib/cjs/tests/api_tests/validation_behavior_locks.test.js.map +1 -0
  103. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts +5 -0
  104. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -0
  105. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js +569 -0
  106. package/lib/cjs/tests/api_tests/vital_review_trigger_action.test.js.map +1 -0
  107. package/lib/cjs/tests/public_endpoint_tests.js +3 -2
  108. package/lib/cjs/tests/public_endpoint_tests.js.map +1 -1
  109. package/lib/cjs/tests/setup.d.ts +16 -0
  110. package/lib/cjs/tests/setup.d.ts.map +1 -1
  111. package/lib/cjs/tests/setup.js +55 -8
  112. package/lib/cjs/tests/setup.js.map +1 -1
  113. package/lib/cjs/tests/setup_isolated.d.ts +9 -0
  114. package/lib/cjs/tests/setup_isolated.d.ts.map +1 -1
  115. package/lib/cjs/tests/setup_isolated.js +23 -2
  116. package/lib/cjs/tests/setup_isolated.js.map +1 -1
  117. package/lib/cjs/tests/socket_tests.d.ts.map +1 -1
  118. package/lib/cjs/tests/socket_tests.js +3 -2
  119. package/lib/cjs/tests/socket_tests.js.map +1 -1
  120. package/lib/cjs/tests/tests.d.ts.map +1 -1
  121. package/lib/cjs/tests/tests.js +180 -166
  122. package/lib/cjs/tests/tests.js.map +1 -1
  123. package/lib/cjs/tests/unit_tests/trigger_title_partials.test.d.ts +3 -0
  124. package/lib/cjs/tests/unit_tests/trigger_title_partials.test.d.ts.map +1 -0
  125. package/lib/cjs/tests/unit_tests/trigger_title_partials.test.js +118 -0
  126. package/lib/cjs/tests/unit_tests/trigger_title_partials.test.js.map +1 -0
  127. package/lib/cjs/tests/webhooks_tests.js +3 -2
  128. package/lib/cjs/tests/webhooks_tests.js.map +1 -1
  129. package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  130. package/lib/esm/enduser.d.ts +2 -0
  131. package/lib/esm/enduser.d.ts.map +1 -1
  132. package/lib/esm/sdk.d.ts +6 -3
  133. package/lib/esm/sdk.d.ts.map +1 -1
  134. package/lib/esm/sdk.js +1 -0
  135. package/lib/esm/sdk.js.map +1 -1
  136. package/lib/esm/session.d.ts +2 -0
  137. package/lib/esm/session.d.ts.map +1 -1
  138. package/lib/esm/tests/api_tests/active_patient_count.test.js +1 -1
  139. package/lib/esm/tests/api_tests/active_patient_count.test.js.map +1 -1
  140. package/lib/esm/tests/api_tests/appointment_rescheduled_trigger.test.js +1 -1
  141. package/lib/esm/tests/api_tests/appointment_rescheduled_trigger.test.js.map +1 -1
  142. package/lib/esm/tests/api_tests/calendar_event_limits.test.js +5 -5
  143. package/lib/esm/tests/api_tests/calendar_event_limits.test.js.map +1 -1
  144. package/lib/esm/tests/api_tests/calendar_event_references_update.test.js +1 -1
  145. package/lib/esm/tests/api_tests/calendar_event_references_update.test.js.map +1 -1
  146. package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js +1 -1
  147. package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
  148. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts +21 -0
  149. package/lib/esm/tests/api_tests/enduser_fields_sync.test.d.ts.map +1 -0
  150. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js +210 -0
  151. package/lib/esm/tests/api_tests/enduser_fields_sync.test.js.map +1 -0
  152. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js +7 -7
  153. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -1
  154. package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js +4 -4
  155. package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js.map +1 -1
  156. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts +32 -0
  157. package/lib/esm/tests/api_tests/load_threads_care_team.test.d.ts.map +1 -0
  158. package/lib/esm/tests/api_tests/load_threads_care_team.test.js +252 -0
  159. package/lib/esm/tests/api_tests/load_threads_care_team.test.js.map +1 -0
  160. package/lib/esm/tests/api_tests/managed_content_enduser_access.test.js +1 -1
  161. package/lib/esm/tests/api_tests/managed_content_enduser_access.test.js.map +1 -1
  162. package/lib/esm/tests/api_tests/managed_content_file_access.test.js +1 -1
  163. package/lib/esm/tests/api_tests/managed_content_file_access.test.js.map +1 -1
  164. package/lib/esm/tests/api_tests/mdi_webhooks.test.d.ts.map +1 -1
  165. package/lib/esm/tests/api_tests/mdi_webhooks.test.js +21 -8
  166. package/lib/esm/tests/api_tests/mdi_webhooks.test.js.map +1 -1
  167. package/lib/esm/tests/api_tests/nutrition_tracking.test.js +1 -1
  168. package/lib/esm/tests/api_tests/nutrition_tracking.test.js.map +1 -1
  169. package/lib/esm/tests/api_tests/openloop_webhooks.test.js +1 -1
  170. package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  171. package/lib/esm/tests/api_tests/programs.test.d.ts +5 -0
  172. package/lib/esm/tests/api_tests/programs.test.d.ts.map +1 -0
  173. package/lib/esm/tests/api_tests/programs.test.js +365 -0
  174. package/lib/esm/tests/api_tests/programs.test.js.map +1 -0
  175. package/lib/esm/tests/api_tests/programs_trigger_filters.test.d.ts +5 -0
  176. package/lib/esm/tests/api_tests/programs_trigger_filters.test.d.ts.map +1 -0
  177. package/lib/esm/tests/api_tests/programs_trigger_filters.test.js +290 -0
  178. package/lib/esm/tests/api_tests/programs_trigger_filters.test.js.map +1 -0
  179. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js +1 -1
  180. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -1
  181. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts +42 -0
  182. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.d.ts.map +1 -0
  183. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js +479 -0
  184. package/lib/esm/tests/api_tests/security/appointment-reschedule-ownership.test.js.map +1 -0
  185. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js +1 -1
  186. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -1
  187. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js +1 -1
  188. package/lib/esm/tests/api_tests/security/enduser-ai-summary-trust-gate.test.js.map +1 -1
  189. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js +1 -1
  190. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js.map +1 -1
  191. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js +1 -1
  192. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -1
  193. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js +3 -3
  194. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -1
  195. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts +32 -0
  196. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.d.ts.map +1 -0
  197. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js +292 -0
  198. package/lib/esm/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.js.map +1 -0
  199. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -1
  200. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +4 -1
  201. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -1
  202. package/lib/esm/tests/api_tests/templated_message_date_fields.test.d.ts +3 -0
  203. package/lib/esm/tests/api_tests/templated_message_date_fields.test.d.ts.map +1 -0
  204. package/lib/esm/tests/api_tests/templated_message_date_fields.test.js +198 -0
  205. package/lib/esm/tests/api_tests/templated_message_date_fields.test.js.map +1 -0
  206. package/lib/esm/tests/api_tests/templated_message_link_in_href.test.d.ts +3 -0
  207. package/lib/esm/tests/api_tests/templated_message_link_in_href.test.d.ts.map +1 -0
  208. package/lib/esm/tests/api_tests/templated_message_link_in_href.test.js +229 -0
  209. package/lib/esm/tests/api_tests/templated_message_link_in_href.test.js.map +1 -0
  210. package/lib/esm/tests/api_tests/templated_message_links.test.d.ts +3 -0
  211. package/lib/esm/tests/api_tests/templated_message_links.test.d.ts.map +1 -0
  212. package/lib/esm/tests/api_tests/templated_message_links.test.js +231 -0
  213. package/lib/esm/tests/api_tests/templated_message_links.test.js.map +1 -0
  214. package/lib/esm/tests/api_tests/time_tracks.test.js +1 -1
  215. package/lib/esm/tests/api_tests/time_tracks.test.js.map +1 -1
  216. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts +5 -0
  217. package/lib/esm/tests/api_tests/trigger_contains_matching.test.d.ts.map +1 -0
  218. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js +390 -0
  219. package/lib/esm/tests/api_tests/trigger_contains_matching.test.js.map +1 -0
  220. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts +5 -0
  221. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.d.ts.map +1 -0
  222. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js +466 -0
  223. package/lib/esm/tests/api_tests/user_logs_restore_deleted.test.js.map +1 -0
  224. package/lib/esm/tests/api_tests/user_portal_settings.test.js +1 -1
  225. package/lib/esm/tests/api_tests/user_portal_settings.test.js.map +1 -1
  226. package/lib/esm/tests/api_tests/validation_behavior_locks.test.d.ts +2 -0
  227. package/lib/esm/tests/api_tests/validation_behavior_locks.test.d.ts.map +1 -0
  228. package/lib/esm/tests/api_tests/validation_behavior_locks.test.js +229 -0
  229. package/lib/esm/tests/api_tests/validation_behavior_locks.test.js.map +1 -0
  230. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts +5 -0
  231. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.d.ts.map +1 -0
  232. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js +564 -0
  233. package/lib/esm/tests/api_tests/vital_review_trigger_action.test.js.map +1 -0
  234. package/lib/esm/tests/public_endpoint_tests.js +3 -2
  235. package/lib/esm/tests/public_endpoint_tests.js.map +1 -1
  236. package/lib/esm/tests/setup.d.ts +16 -0
  237. package/lib/esm/tests/setup.d.ts.map +1 -1
  238. package/lib/esm/tests/setup.js +53 -7
  239. package/lib/esm/tests/setup.js.map +1 -1
  240. package/lib/esm/tests/setup_isolated.d.ts +9 -0
  241. package/lib/esm/tests/setup_isolated.d.ts.map +1 -1
  242. package/lib/esm/tests/setup_isolated.js +21 -1
  243. package/lib/esm/tests/setup_isolated.js.map +1 -1
  244. package/lib/esm/tests/socket_tests.d.ts.map +1 -1
  245. package/lib/esm/tests/socket_tests.js +3 -2
  246. package/lib/esm/tests/socket_tests.js.map +1 -1
  247. package/lib/esm/tests/tests.d.ts.map +1 -1
  248. package/lib/esm/tests/tests.js +180 -166
  249. package/lib/esm/tests/tests.js.map +1 -1
  250. package/lib/esm/tests/unit_tests/trigger_title_partials.test.d.ts +3 -0
  251. package/lib/esm/tests/unit_tests/trigger_title_partials.test.d.ts.map +1 -0
  252. package/lib/esm/tests/unit_tests/trigger_title_partials.test.js +115 -0
  253. package/lib/esm/tests/unit_tests/trigger_title_partials.test.js.map +1 -0
  254. package/lib/esm/tests/webhooks_tests.js +3 -2
  255. package/lib/esm/tests/webhooks_tests.js.map +1 -1
  256. package/lib/tsconfig.tsbuildinfo +1 -1
  257. package/package.json +22 -19
  258. package/src/sdk.ts +5 -1
  259. package/src/tests/api_tests/active_patient_count.test.ts +1 -1
  260. package/src/tests/api_tests/appointment_rescheduled_trigger.test.ts +1 -1
  261. package/src/tests/api_tests/calendar_event_references_update.test.ts +1 -1
  262. package/src/tests/api_tests/enduser_cross_access_isolation.test.ts +1 -1
  263. package/src/tests/api_tests/enduser_login_rate_limits.test.ts +7 -7
  264. package/src/tests/api_tests/enduser_session_invalidation.test.ts +4 -4
  265. package/src/tests/api_tests/managed_content_enduser_access.test.ts +1 -1
  266. package/src/tests/api_tests/managed_content_file_access.test.ts +1 -1
  267. package/src/tests/api_tests/mdi_webhooks.test.ts +9 -0
  268. package/src/tests/api_tests/nutrition_tracking.test.ts +1 -1
  269. package/src/tests/api_tests/openloop_webhooks.test.ts +1 -1
  270. package/src/tests/api_tests/security/allowed-paths-confinement.test.ts +1 -1
  271. package/src/tests/api_tests/security/booking-info-idor-token-mint.test.ts +1 -1
  272. package/src/tests/api_tests/security/enduser-ai-summary-trust-gate.test.ts +1 -1
  273. package/src/tests/api_tests/security/enduser-cross-create.test.ts +1 -1
  274. package/src/tests/api_tests/security/enduser-write-restrictions.test.ts +1 -1
  275. package/src/tests/api_tests/security/invite-user-enumeration.test.ts +3 -3
  276. package/src/tests/api_tests/security/rich-text-answer-sanitized-on-write.test.ts +197 -0
  277. package/src/tests/api_tests/security/sanitize-user-html-with-iframes.test.ts +4 -1
  278. package/src/tests/api_tests/templated_message_date_fields.test.ts +146 -0
  279. package/src/tests/api_tests/templated_message_links.test.ts +192 -0
  280. package/src/tests/api_tests/time_tracks.test.ts +1 -1
  281. package/src/tests/api_tests/user_portal_settings.test.ts +1 -1
  282. package/src/tests/api_tests/validation_behavior_locks.test.ts +204 -0
  283. package/src/tests/public_endpoint_tests.ts +3 -2
  284. package/src/tests/setup.ts +43 -7
  285. package/src/tests/setup_isolated.ts +10 -0
  286. package/src/tests/socket_tests.ts +3 -2
  287. package/src/tests/tests.ts +10 -2
  288. package/src/tests/webhooks_tests.ts +4 -2
  289. package/test_generated.pdf +0 -0
  290. package/tsconfig.cjs.json +17 -0
@@ -0,0 +1,146 @@
1
+ require('source-map-support').install();
2
+
3
+ import {
4
+ assert_checks,
5
+ Check,
6
+ setup_isolated_tenant,
7
+ } from "../setup_isolated"
8
+
9
+ const host = process.env.API_URL || 'http://localhost:8080' as const
10
+
11
+ /**
12
+ * Coverage for how `get_templated_message` renders enduser field values that LOOK like dates.
13
+ *
14
+ * The rule: a value stored as text renders as the author stored it. `message_templating.ts` only
15
+ * reformats an actual `Date` object, and a custom field is always a string.
16
+ *
17
+ * This exists because that rule was silently broken IN PRODUCTION, and had been for a long time.
18
+ * The templating code read `if (fieldValue instanceof Date || isDate(fieldValue))`. Validator
19
+ * 13.11.0's `isDate` was timezone-dependent — it compared a UTC-parsed date against a local-time
20
+ * `.getDate()` — so it returned false on a US developer's machine but TRUE in UTC, which is what
21
+ * the servers run. A date of birth stored as "1985-03-12" therefore took the Date branch in
22
+ * production, was parsed as UTC midnight and localized to the enduser's timezone, and rendered in a
23
+ * patient-facing SMS or email as "3/11/1985, 7:00:00 PM" — the PREVIOUS day, with a time nobody
24
+ * entered. A January date lost a year: "2024-01-01" rendered "12/31/2023".
25
+ *
26
+ * The validator bump did not cause this; it made it reproducible locally, which is how it surfaced.
27
+ * Removing the `isDate` half of the condition fixes it in every timezone.
28
+ *
29
+ * This is the ONE intentional production behavior change in an otherwise behavior-neutral
30
+ * dependency PR, made deliberately rather than shipping wrong dates to patients for another
31
+ * cycle. Every other validator touched here was pinned to production's existing behavior.
32
+ *
33
+ * So the assertions below are about the day not moving and no clock time appearing, which is what a
34
+ * patient would actually notice. A pure-function test could not have caught this: the function that
35
+ * does the work is module-private and the endpoint is the real surface.
36
+ *
37
+ * Runs standalone or as part of the main suite:
38
+ * npm run test:modular -- api_tests/templated_message_date_fields
39
+ */
40
+
41
+ /** Values a customer realistically stores in a date-ish custom field, and what must come back. */
42
+ const DATE_SHAPED_VALUES = [
43
+ { label: 'ISO date (the shape that regressed)', value: '1985-03-12' },
44
+ { label: 'ISO date, recent', value: '2024-01-01' },
45
+ { label: 'slash-separated date', value: '1999/12/31' },
46
+ { label: 'MM-DD-YYYY (the dateOfBirth format)', value: '03-12-1985' },
47
+ { label: 'ISO datetime string', value: '2024-06-15T14:30:00.000Z' },
48
+ { label: 'leap day', value: '2000-02-29' },
49
+ ]
50
+
51
+ /** A rendered date-only value must not have grown a clock time or a timezone abbreviation. */
52
+ const LOOKS_REFORMATTED = /\d{1,2}:\d{2}|\bAM\b|\bPM\b|\bE[SD]T\b|\bUTC\b|\bGMT\b/
53
+
54
+ const templated_message_date_field_checks = async (): Promise<Check[]> => {
55
+ const checks: Check[] = []
56
+ const tenant = await setup_isolated_tenant({ users: [{ isAdmin: true }] })
57
+
58
+ try {
59
+ const { sdk } = tenant
60
+ const userId = tenant.users[0].id
61
+
62
+ const fields = Object.fromEntries(DATE_SHAPED_VALUES.map((c, i) => [`DateField${i}`, c.value]))
63
+ const enduser = await sdk.api.endusers.createOne({
64
+ fname: 'Date',
65
+ lname: 'Fields',
66
+ timezone: 'America/New_York', // the timezone the shifted rendering localized into
67
+ fields,
68
+ })
69
+
70
+ for (let i = 0; i < DATE_SHAPED_VALUES.length; i++) {
71
+ const { label, value } = DATE_SHAPED_VALUES[i]
72
+ const { plaintext } = await sdk.api.templates.get_templated_message({
73
+ enduserId: enduser.id,
74
+ userId,
75
+ channel: 'Chat',
76
+ message: `[{{enduser.DateField${i}}}]`,
77
+ })
78
+
79
+ checks.push({
80
+ label: `${label} renders exactly as stored`,
81
+ pass: plaintext === `[${value}]`,
82
+ detail: `stored ${JSON.stringify(value)}, rendered ${JSON.stringify(plaintext)}`,
83
+ })
84
+ // A second, independent signal for the specific symptom, so this still catches a shifted
85
+ // rendering even if the equality check above is ever loosened. Only meaningful for values
86
+ // that carry no time of their own — an ISO datetime legitimately contains one.
87
+ if (!LOOKS_REFORMATTED.test(value)) {
88
+ checks.push({
89
+ label: `${label} does not gain a clock time`,
90
+ pass: !LOOKS_REFORMATTED.test(plaintext),
91
+ detail: `rendered ${JSON.stringify(plaintext)}`,
92
+ })
93
+ }
94
+ }
95
+
96
+ // The specific patient-visible symptom, asserted on its own so a failure names it.
97
+ const { plaintext: dob } = await sdk.api.templates.get_templated_message({
98
+ enduserId: enduser.id,
99
+ userId,
100
+ channel: 'Chat',
101
+ message: `DOB: {{enduser.DateField0}}`,
102
+ })
103
+ checks.push({
104
+ label: 'a date of birth does not render as the previous day',
105
+ pass: dob.includes('1985-03-12') && !dob.includes('3/11/1985'),
106
+ detail: `rendered ${JSON.stringify(dob)}`,
107
+ })
108
+
109
+ // Positive control: if templating itself were broken, every check above would pass vacuously
110
+ // (an unreplaced `{{enduser.X}}` is neither reformatted nor time-stamped).
111
+ const { plaintext: control } = await sdk.api.templates.get_templated_message({
112
+ enduserId: enduser.id,
113
+ userId,
114
+ channel: 'Chat',
115
+ message: `{{enduser.fname}} {{enduser.lname}}`,
116
+ })
117
+ checks.push({
118
+ label: 'control: non-date fields are still templated at all',
119
+ pass: control === 'Date Fields',
120
+ detail: `rendered ${JSON.stringify(control)}`,
121
+ })
122
+
123
+ return checks
124
+ } finally {
125
+ await tenant.teardown()
126
+ }
127
+ }
128
+
129
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
130
+ export const templated_message_date_field_tests = async () => {
131
+ assert_checks('Templated message date-shaped fields', await templated_message_date_field_checks())
132
+ }
133
+
134
+ // Allow running this test file independently
135
+ if (require.main === module) {
136
+ console.log(`🌐 Using API URL: ${host}`)
137
+ templated_message_date_field_tests()
138
+ .then(() => {
139
+ console.log("✅ Templated message date field test suite completed successfully")
140
+ process.exit(0)
141
+ })
142
+ .catch((error) => {
143
+ console.error("❌ Templated message date field test suite failed:", error)
144
+ process.exit(1)
145
+ })
146
+ }
@@ -0,0 +1,192 @@
1
+ require('source-map-support').install();
2
+
3
+ import {
4
+ assert_checks,
5
+ Check,
6
+ setup_isolated_tenant,
7
+ } from "../setup_isolated"
8
+
9
+ const host = process.env.API_URL || 'http://localhost:8080' as const
10
+
11
+ /**
12
+ * Link templating through the real `templates.get_templated_message` — the same endpoint the worker
13
+ * calls on every outbound email and SMS.
14
+ *
15
+ * The exhaustive coverage of this path lives in the API's own module test
16
+ * (`packages/private/api/api/modules/message_templating.test.ts`), which stubs the database so a case
17
+ * costs microseconds. This suite exists to prove those stubs still describe the real thing: that a
18
+ * rendered link really is a `/r/<token>` redirect, and that `linkOpenTrackingIds` really is one id per
19
+ * redirect record created.
20
+ *
21
+ * Deliberately small. Every case here writes real `url_redirects` rows, and `teardown_tenant` does not
22
+ * remove them — that collection is in neither `schema` nor `getInternalQueries()` — so each extra case
23
+ * leaves rows behind in the database forever.
24
+ *
25
+ * Runs standalone or as part of the main suite:
26
+ * npm run test:modular -- api_tests/templated_message_links
27
+ */
28
+
29
+ /** A tracked link is `<portal or api url>/r/<token>`; the token itself is not predictable. */
30
+ const REDIRECT = /https?:\/\/[^\s"'<>]+\/r\/[A-Za-z0-9_-]+/
31
+ /** Bound on the one case that used to hang the request, so a regression fails instead of stalling. */
32
+ const CLIENT_WAIT_MS = 20_000
33
+ const LINK = 'https://example.com/landing'
34
+
35
+ const templated_message_link_checks = async (): Promise<Check[]> => {
36
+ const checks: Check[] = []
37
+ const tenant = await setup_isolated_tenant({ users: [{ isAdmin: true }] })
38
+
39
+ try {
40
+ const { sdk } = tenant
41
+ const userId = tenant.users[0].id
42
+ const enduser = await sdk.api.endusers.createOne({ fname: 'Link', lname: 'Templating' })
43
+
44
+ const templated = (message: string, html: string) => sdk.api.templates.get_templated_message({
45
+ enduserId: enduser.id, userId, channel: 'Email', message, html,
46
+ })
47
+
48
+ // 1. one link: an anchor in html, "text (url)" in plaintext, one tracking id
49
+ {
50
+ const body = `Go: {${LINK}}[Click]`
51
+ const { html, plaintext, linkOpenTrackingIds } = await templated(body, `<p>${body}</p>`)
52
+
53
+ checks.push({
54
+ label: 'html renders an anchor whose href is a /r/ redirect',
55
+ pass: new RegExp(`^<p>Go: <a href="${REDIRECT.source}">Click</a></p>$`).test(html),
56
+ detail: `rendered ${JSON.stringify(html)}`,
57
+ })
58
+ checks.push({
59
+ label: 'plaintext appends the same redirect in parentheses',
60
+ pass: new RegExp(`^Go: Click \\(${REDIRECT.source}\\)$`).test(plaintext),
61
+ detail: `rendered ${JSON.stringify(plaintext)}`,
62
+ })
63
+ checks.push({
64
+ label: 'one link creates exactly one redirect record',
65
+ pass: linkOpenTrackingIds.length === 1,
66
+ detail: `linkOpenTrackingIds=${JSON.stringify(linkOpenTrackingIds)}`,
67
+ })
68
+ }
69
+
70
+ // 2. several links: one record each, all replaced
71
+ {
72
+ const body = `One {${LINK}/a}[A], two {${LINK}/b}[B], three {${LINK}/c}[C]`
73
+ const { html, linkOpenTrackingIds } = await templated(body, `<p>${body}</p>`)
74
+
75
+ checks.push({
76
+ label: 'every link in one message is replaced',
77
+ pass: (html.match(/<a href="/g) || []).length === 3 && !html.includes('{'),
78
+ detail: `rendered ${JSON.stringify(html)}`,
79
+ })
80
+ checks.push({
81
+ label: 'three links create exactly three redirect records',
82
+ pass: linkOpenTrackingIds.length === 3,
83
+ detail: `linkOpenTrackingIds=${JSON.stringify(linkOpenTrackingIds)}`,
84
+ })
85
+ }
86
+
87
+ // 3. $LINK_ONLY: the bare redirect url, no anchor
88
+ {
89
+ const body = `Go: {${LINK}}[$LINK_ONLY]`
90
+ const { html, plaintext, linkOpenTrackingIds } = await templated(body, `<p>${body}</p>`)
91
+
92
+ checks.push({
93
+ label: '$LINK_ONLY substitutes the bare redirect url',
94
+ pass: new RegExp(`^<p>Go: ${REDIRECT.source}</p>$`).test(html)
95
+ && new RegExp(`^Go: ${REDIRECT.source}$`).test(plaintext),
96
+ detail: `html ${JSON.stringify(html)}, plaintext ${JSON.stringify(plaintext)}`,
97
+ })
98
+ checks.push({
99
+ label: '$LINK_ONLY still creates its redirect record',
100
+ pass: linkOpenTrackingIds.length === 1,
101
+ detail: `linkOpenTrackingIds=${JSON.stringify(linkOpenTrackingIds)}`,
102
+ })
103
+ }
104
+
105
+ // 4. a link merge field resolves to the same {url}[text] form, then to an anchor
106
+ {
107
+ const form = await sdk.api.forms.createOne({ title: `Link templating ${Date.now()}` })
108
+ try {
109
+ const body = `Start here: {{forms.${form.id}.link:Begin}}`
110
+ const { html, linkOpenTrackingIds } = await templated(body, `<p>${body}</p>`)
111
+
112
+ checks.push({
113
+ label: 'a form link merge field becomes an anchor on a redirect',
114
+ pass: new RegExp(`^<p>Start here: <a href="${REDIRECT.source}">Begin</a></p>$`).test(html),
115
+ detail: `rendered ${JSON.stringify(html)}`,
116
+ })
117
+ checks.push({
118
+ label: 'the form link creates exactly one redirect record',
119
+ pass: linkOpenTrackingIds.length === 1,
120
+ detail: `linkOpenTrackingIds=${JSON.stringify(linkOpenTrackingIds)}`,
121
+ })
122
+ } finally {
123
+ await sdk.api.forms.deleteOne(form.id)
124
+ }
125
+ }
126
+
127
+ // 5. The loop-safety case, end to end. A space between the two halves of a link template is one
128
+ // find_link matches but the replacement key cannot, and this request used to never come back:
129
+ // the server looped on that same match, inserting a url_redirects row every time round. The
130
+ // client-side bound is what turns a regression here into a failure rather than a hung suite.
131
+ {
132
+ const body = `Go: {${LINK}} [Click]`
133
+ const outcome = await Promise.race([
134
+ templated(body, `<p>${body}</p>`).then(r => r).catch(e => ({ error: e?.message ?? String(e) } as any)),
135
+ new Promise<any>(resolve => setTimeout(() => resolve({ timedOut: true }), CLIENT_WAIT_MS)),
136
+ ])
137
+
138
+ checks.push({
139
+ label: 'a template the server cannot replace still returns a response',
140
+ pass: !outcome.timedOut && !outcome.error,
141
+ detail: outcome.timedOut
142
+ ? `no response within ${CLIENT_WAIT_MS}ms — the templating loop stopped terminating`
143
+ : outcome.error ? `errored: ${outcome.error}` : 'responded',
144
+ })
145
+ if (!outcome.timedOut && !outcome.error) {
146
+ checks.push({
147
+ label: 'it is left literal in both the html and the plaintext',
148
+ pass: outcome.html === `<p>${body}</p>` && outcome.plaintext === body,
149
+ detail: `html ${JSON.stringify(outcome.html)}, plaintext ${JSON.stringify(outcome.plaintext)}`,
150
+ })
151
+ checks.push({
152
+ label: 'and it creates no redirect records at all',
153
+ pass: outcome.linkOpenTrackingIds.length === 0,
154
+ detail: `linkOpenTrackingIds=${JSON.stringify(outcome.linkOpenTrackingIds)}`,
155
+ })
156
+ }
157
+ }
158
+
159
+ // Positive control: a body with no link syntax is left alone and creates nothing.
160
+ {
161
+ const { html, linkOpenTrackingIds } = await templated('No links here', '<p>No links here</p>')
162
+ checks.push({
163
+ label: 'control: a body without link syntax is untouched and creates no records',
164
+ pass: html === '<p>No links here</p>' && linkOpenTrackingIds.length === 0,
165
+ detail: `rendered ${JSON.stringify(html)}, linkOpenTrackingIds=${JSON.stringify(linkOpenTrackingIds)}`,
166
+ })
167
+ }
168
+
169
+ return checks
170
+ } finally {
171
+ await tenant.teardown()
172
+ }
173
+ }
174
+
175
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
176
+ export const templated_message_link_tests = async () => {
177
+ assert_checks('Templated message links', await templated_message_link_checks())
178
+ }
179
+
180
+ // Allow running this test file independently
181
+ if (require.main === module) {
182
+ console.log(`🌐 Using API URL: ${host}`)
183
+ templated_message_link_tests()
184
+ .then(() => {
185
+ console.log("✅ Templated message link test suite completed successfully")
186
+ process.exit(0)
187
+ })
188
+ .catch((error) => {
189
+ console.error("❌ Templated message link test suite failed:", error)
190
+ process.exit(1)
191
+ })
192
+ }
@@ -6,7 +6,7 @@ import { setup_tests } from "../setup"
6
6
 
7
7
  const log = console.log
8
8
 
9
- const host = process.env.REACT_APP_TELLESCOPE_API_URL || 'http://localhost:8080'
9
+ const host = process.env.API_URL || process.env.REACT_APP_TELLESCOPE_API_URL || 'http://localhost:8080'
10
10
 
11
11
  // Helper to assert that an async function throws an error
12
12
  const assert_throws = async (fn: () => Promise<any>, description: string) => {
@@ -10,7 +10,7 @@ import {
10
10
  import { setup_tests } from "../setup"
11
11
 
12
12
  const host = process.env.API_URL || 'http://localhost:8080' as const
13
- const businessId = '60398b1131a295e64f084ff6'
13
+ const businessId = process.env.TEST_BUSINESS_ID || '60398b1131a295e64f084ff6'
14
14
 
15
15
  // Main test function that can be called independently
16
16
  export const user_portal_settings_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
@@ -0,0 +1,204 @@
1
+ import { dateValidator, dateOptionalOrEmptyStringValidator, dateValidatorOptional, slugValidator, urlValidator } from "@tellescope/validation"
2
+
3
+ // Pins the accept-sets of the validators whose behavior is delegated to the `validator` package, so
4
+ // a dependency upgrade cannot move what the API accepts without a test failing.
5
+ //
6
+ // These assertions describe PRODUCTION — deliberately, including the parts that are wrong. Two of
7
+ // them are: a bare `YYYY-MM-DD` is rejected while an ISO datetime is accepted, and an unparseable
8
+ // string is accepted and persisted as an `Invalid Date`. Both predate this work and both are
9
+ // API-visible across ~135 schema fields, so changing them belongs in its own PR. Pinning them is
10
+ // what makes this file useful: it is proof of how the deployed API behaves, not an aspiration.
11
+ //
12
+ // Why it was needed. Validator 13.11.0's `isDate` was timezone-dependent — its last step compared a
13
+ // UTC-parsed date against a local-time `.getDate()` — so it answered false west of Greenwich and
14
+ // true in UTC. The servers run UTC, so the behavior pinned below is what production has been doing,
15
+ // while the same code on a US developer's machine did the opposite. 13.12.0 fixed that (#2257), and
16
+ // 13.15.35 was verified to return exactly what 13.11.0 returned under UTC, across 26 inputs and four
17
+ // timezones. So every assertion here holds in EVERY timezone — that is the point of the file, and
18
+ // the reason it can be trusted as a statement about production while running on a laptop.
19
+ //
20
+ // The `isSlug` half is different: 13.15.35 genuinely narrowed it (lowercase-only, alphanumeric ends,
21
+ // 1-character minimum), which would have moved the subdomain accept-set. `validation.ts` pins
22
+ // 13.11.0's pattern inline instead, and the checks below are that pattern.
23
+ //
24
+ // The `isURL` half (section 5) is neither: 13.11.0 and 13.15.35 were compared across 37 URL inputs
25
+ // and agreed on every one, so nothing was at risk in that upgrade and nothing needed pinning inline.
26
+ // It is here as a forward guard — webhook destinations are customer-configured and an accept-set that
27
+ // narrows silently breaks live integrations — and because the staging QA asserted this behavior with
28
+ // no test behind it. `isURL` reads no clock, so that section has no timezone caveat.
29
+ //
30
+ // Pure-function test — no Session needed. Runs as part of the main suite and standalone:
31
+ // npm run test:modular -- api_tests/validation_behavior_locks
32
+ // TZ=UTC npm run test:modular -- api_tests/validation_behavior_locks (must give the same result)
33
+
34
+ // Every assertion runs before anything throws, so one run reports the complete diff rather than the
35
+ // first case that happened to trip.
36
+ const failures: string[] = []
37
+
38
+ const accepts = (label: string, validate: (v: any) => any, value: any) => {
39
+ try { validate(value) }
40
+ catch (err: any) { failures.push(`${label}: expected to ACCEPT ${JSON.stringify(value)}, but it was rejected (${err?.message || err})`) }
41
+ }
42
+
43
+ const rejects = (label: string, validate: (v: any) => any, value: any) => {
44
+ let accepted = false
45
+ try { validate(value); accepted = true }
46
+ catch { /* expected */ }
47
+ if (accepted) failures.push(`${label}: expected to REJECT ${JSON.stringify(value)}, but it was accepted`)
48
+ }
49
+
50
+ export const validation_behavior_lock_tests = async () => {
51
+ console.log(`Running validation behavior lock tests (TZ=${process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone})`)
52
+
53
+ const date = dateValidator.validate()
54
+ const dateOptional = dateValidatorOptional.validate()
55
+ const dateOrEmpty = dateOptionalOrEmptyStringValidator.validate()
56
+
57
+ // -----------------------------------------------------------------------------------------------
58
+ // 1. Date fields: ISO datetimes are accepted
59
+ // -----------------------------------------------------------------------------------------------
60
+ // What the SDK sends, since JSON.stringify turns a Date into one of these.
61
+ for (const value of [
62
+ '2024-06-15T14:30:00Z',
63
+ '2024-06-15T00:00:00.000Z',
64
+ '2024-06-15T14:30:00+05:00',
65
+ ]) {
66
+ accepts('dateValidator', date, value)
67
+ accepts('dateValidatorOptional', dateOptional, value)
68
+ accepts('dateOptionalOrEmptyStringValidator', dateOrEmpty, value)
69
+ }
70
+
71
+ // Epoch milliseconds round-trip through `new Date()`.
72
+ accepts('dateValidator', date, 1718400000000)
73
+
74
+ // The empty string is the "cleared" value for the two lenient variants only.
75
+ accepts('dateOptionalOrEmptyStringValidator', dateOrEmpty, '')
76
+ rejects('dateValidator', date, '')
77
+
78
+ // -----------------------------------------------------------------------------------------------
79
+ // 2. Date fields: a bare calendar date is REJECTED
80
+ // -----------------------------------------------------------------------------------------------
81
+ // Surprising, and not a typo. `isDate` returns true for these and the check is inverted, so they
82
+ // 400. This is long-standing production behavior; it is pinned, not endorsed. A client sending
83
+ // `"1985-03-12"` to a date field gets an error and must send an ISO datetime instead.
84
+ for (const value of ['1985-03-12', '2024-01-01', '2026-10-01', '2000-02-29', '1999/12/31']) {
85
+ rejects('dateValidator', date, value)
86
+ rejects('dateValidatorOptional', dateOptional, value)
87
+ }
88
+
89
+ // A real Date object is rejected by the same mechanism (isDate is true for one). Nothing sends a
90
+ // Date over HTTP, so this only reaches internal callers — but the three validators disagree here:
91
+ // the `dateOrEmpty` variant coerces with `.toString()` first, producing "Sat Jun 15 2024 ...",
92
+ // which isDate calls false, so it accepts. Pinned because it is a real inconsistency.
93
+ rejects('dateValidator', date, new Date('2024-06-15T14:30:00Z'))
94
+ rejects('dateValidatorOptional', dateOptional, new Date('2024-06-15T14:30:00Z'))
95
+ accepts('dateOptionalOrEmptyStringValidator', dateOrEmpty, new Date('2024-06-15T14:30:00Z'))
96
+
97
+ // -----------------------------------------------------------------------------------------------
98
+ // 3. Date fields: an unparseable string is ACCEPTED and stored as an Invalid Date
99
+ // -----------------------------------------------------------------------------------------------
100
+ // A data-corruption path, pinned so it is documented and so a future fix is a deliberate, visible
101
+ // change rather than a silent one. `new Date('not a date')` is an Invalid Date, and it is what
102
+ // gets written.
103
+ for (const value of ['not a date', 'hello world', 'tomorrow', '??']) {
104
+ accepts('dateValidator', date, value)
105
+ accepts('dateValidatorOptional', dateOptional, value)
106
+ accepts('dateOptionalOrEmptyStringValidator', dateOrEmpty, value)
107
+ }
108
+
109
+ // -----------------------------------------------------------------------------------------------
110
+ // 4. Organization subdomain
111
+ // -----------------------------------------------------------------------------------------------
112
+ const slug = slugValidator.validate()
113
+
114
+ // Valid, and must stay valid — 13.15.35's isSlug would reject the mixed-case ones.
115
+ for (const value of ['acme', 'acme-health', 'tellescope-demo', 'demo_org_1', 'Acme', 'My-Org', 'TellescopeDemo', 'abc']) {
116
+ accepts('slugValidator', slug, value)
117
+ }
118
+
119
+ // Also accepted today, though none of these are valid DNS labels. Pinned so the upgrade does not
120
+ // quietly change them; tightening is a separate decision.
121
+ for (const value of ['acme.health', 'org/sub', 'my@org', 'org?x=1']) {
122
+ accepts('slugValidator', slug, value)
123
+ }
124
+
125
+ // Rejected today — note 13.15.35's isSlug would ACCEPT the 1- and 2-character cases.
126
+ for (const value of ['a', 'ab', 'my--org', 'my__org', '-acme', 'acme-', 'org name']) {
127
+ rejects('slugValidator', slug, value)
128
+ }
129
+
130
+ // -----------------------------------------------------------------------------------------------
131
+ // 5. Webhook and other URL fields
132
+ // -----------------------------------------------------------------------------------------------
133
+ // `urlValidator` is a thin `isURL` passthrough behind `build_validator`, used for webhook
134
+ // subscriptions (schema `webhooks.url`) among others. It is a SYNTAX check only — refusing internal
135
+ // destinations is a separate layer (`api/modules/outbound_url.ts`), which is why several things
136
+ // accepted here are refused at delivery time.
137
+ const url = urlValidator.validate()
138
+
139
+ // Ports and query strings, the cases the staging QA calls out.
140
+ for (const value of [
141
+ 'https://example.com:8443/hook',
142
+ 'https://example.com/hook?a=1&b=two',
143
+ 'https://example.com:8443/hook?a=1',
144
+ 'https://example.com/a?b=c&d=%7B%22x%22%3A1%7D', // percent-encoded JSON in the query
145
+ 'https://example.com:443/hook',
146
+ 'https://example.com:8443', // port, no path
147
+ 'https://example.com/hook#frag',
148
+ 'HTTPS://EXAMPLE.COM/Hook',
149
+ 'https://[::1]:8443/hook',
150
+ ]) {
151
+ accepts('urlValidator', url, value)
152
+ }
153
+
154
+ // Accepted, but quirks rather than intent — pinned like the date quirks above so that tightening any
155
+ // of them is a deliberate, visible change.
156
+ // - schemeless: values of this shape exist in the webhooks collection today (outbound_url.ts:213)
157
+ // - ftp: isURL's default protocol list is http/https/ftp
158
+ // - the last two are refused by assertPublicHttpUrl at delivery, not here
159
+ for (const value of [
160
+ 'api.customer.com/hook',
161
+ 'ftp://example.com/x',
162
+ 'http://10.0.0.1/hook',
163
+ 'https://user:pass@example.com/hook',
164
+ ]) {
165
+ accepts('urlValidator', url, value)
166
+ }
167
+
168
+ // Rejected. `localhost` fails isURL's require_tld, which defaults on — a local webhook receiver has
169
+ // to be configured as 127.0.0.1, and that is why the SSRF suite binds loopback by address.
170
+ for (const value of [
171
+ 'http://localhost:8080/hook',
172
+ 'localhost:8080/hook',
173
+ 'https://example.com:65536/hook',
174
+ 'https://example.com:0/hook',
175
+ 'https://example.com:abc/x',
176
+ 'javascript:alert(1)',
177
+ 'file:///etc/passwd',
178
+ 'mailto:a@b.com',
179
+ '//example.com/hook',
180
+ 'not a url',
181
+ 'https://example.com/hook with space',
182
+ 'https://example.com./hook', // trailing dot
183
+ ]) {
184
+ rejects('urlValidator', url, value)
185
+ }
186
+
187
+ // Non-string and absent values. The empty string is rejected by build_validator's non-empty rule
188
+ // rather than by isURL, so it is pinned separately from the malformed strings above.
189
+ rejects('urlValidator', url, '')
190
+ rejects('urlValidator', url, 123)
191
+ rejects('urlValidator', url, null)
192
+ rejects('urlValidator', url, undefined)
193
+
194
+ if (failures.length) {
195
+ throw new Error(`${failures.length} validation behavior lock(s) broken:\n - ` + failures.join('\n - '))
196
+ }
197
+ console.log("✅ validation behavior lock tests passed")
198
+ }
199
+
200
+ if (require.main === module) {
201
+ validation_behavior_lock_tests()
202
+ .then(() => { console.log("✅ suite completed"); process.exit(0) })
203
+ .catch((err) => { console.error("❌ suite failed:", err); process.exit(1) })
204
+ }
@@ -9,6 +9,7 @@ import {
9
9
  } from "@tellescope/utilities"
10
10
  import { Session, /* APIQuery */ } from "../sdk"
11
11
  import { PublicEndpoints } from "../public"
12
+ import { reset_test_tenant } from "./setup"
12
13
 
13
14
  const host = process.env.API_URL || 'http://localhost:8080'
14
15
  const [email, password] = [process.env.TEST_EMAIL, process.env.TEST_PASSWORD]
@@ -18,7 +19,7 @@ const sdkPub = PublicEndpoints({ host })
18
19
 
19
20
  if (!(email && password)) {
20
21
  console.error("Set TEST_EMAIL and TEST_PASSWORD")
21
- process.exit()
22
+ process.exit(1) // 1, not 0: an unconfigured run must not report success
22
23
  }
23
24
 
24
25
  const enduser_login_tests = async () => {
@@ -59,7 +60,7 @@ const enduser_login_tests = async () => {
59
60
 
60
61
  try {
61
62
  await sdk.authenticate(email, password)
62
- await sdk.reset_db()
63
+ await reset_test_tenant(sdk)
63
64
 
64
65
  await enduser_login_tests()
65
66
  } catch(err) {