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