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