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