@tellescope/sdk 1.256.13 → 1.256.15
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/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js +1 -1
- package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js +388 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js +3 -3
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +20 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.js +272 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +103 -162
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +336 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +354 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +389 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +324 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +282 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +249 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +281 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js +390 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js +1003 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js +323 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js +335 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +237 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js +349 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +314 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js +362 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js +366 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js +248 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js +138 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +201 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js +278 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js +155 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js +88 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +230 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +242 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js +148 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js +388 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js +222 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +354 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js +442 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js +156 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +329 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/webhook_error_handling.test.js +2 -2
- package/lib/cjs/tests/api_tests/webhook_error_handling.test.js.map +1 -1
- package/lib/cjs/tests/setup_isolated.d.ts +68 -0
- package/lib/cjs/tests/setup_isolated.d.ts.map +1 -0
- package/lib/cjs/tests/setup_isolated.js +232 -0
- package/lib/cjs/tests/setup_isolated.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +287 -227
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.js +89 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.js.map +1 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +140 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
- package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js +1 -1
- package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js +383 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js +3 -3
- package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/esm/tests/api_tests/organization_signup.test.d.ts +20 -0
- package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/organization_signup.test.js +264 -0
- package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -0
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +104 -163
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +332 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +350 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +382 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +317 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +277 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +244 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +276 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js +386 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js +996 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js +316 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js +331 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +233 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js +345 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +310 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js +358 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js +362 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js +243 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js +134 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +194 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js +271 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js +151 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.js +84 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +223 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +238 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js +144 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js +381 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js +218 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +350 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js +435 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.js +149 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +324 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
- package/lib/esm/tests/api_tests/webhook_error_handling.test.js +2 -2
- package/lib/esm/tests/api_tests/webhook_error_handling.test.js.map +1 -1
- package/lib/esm/tests/setup_isolated.d.ts +68 -0
- package/lib/esm/tests/setup_isolated.d.ts.map +1 -0
- package/lib/esm/tests/setup_isolated.js +221 -0
- package/lib/esm/tests/setup_isolated.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +287 -227
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.js +85 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.js.map +1 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +113 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/auto_merge_form_submission.test.ts +1 -1
- package/src/tests/api_tests/enduser_observations_auto_review.test.ts +358 -0
- package/src/tests/api_tests/integrations_redacted.test.ts +1 -1
- package/src/tests/api_tests/organization_signup.test.ts +240 -0
- package/src/tests/api_tests/purchase_made_trigger.test.ts +29 -41
- package/src/tests/api_tests/security/add-to-journey-foreign-enduserid.test.ts +207 -0
- package/src/tests/api_tests/security/{F-0005-ai-conversations-rbac.test.ts → ai-conversations-rbac.test.ts} +14 -14
- package/src/tests/api_tests/security/{F-0166-allowed-paths-wildcard-bypass.test.ts → allowed-paths-confinement.test.ts} +11 -11
- package/src/tests/api_tests/security/automated-actions-process-gate.test.ts +237 -0
- package/src/tests/api_tests/security/booking-info-idor-token-mint.test.ts +224 -0
- package/src/tests/api_tests/security/{F-0053-cascade-role-rename-cross-tenant.test.ts → cascade-role-rename-cross-tenant.test.ts} +10 -10
- package/src/tests/api_tests/security/{F-0001-data-sync-redaction-bypass.test.ts → data-sync-redaction-bypass.test.ts} +9 -9
- package/src/tests/api_tests/security/{F-0006-develop-health-benefit-verification-rbac.test.ts → develop-health-benefit-verification-rbac.test.ts} +7 -8
- package/src/tests/api_tests/security/{F-0116-F-0119-enduser-cross-create.test.ts → enduser-cross-create.test.ts} +25 -25
- package/src/tests/api_tests/security/{F-0106-F-0110-enduser-write-restrictions.test.ts → enduser-write-restrictions.test.ts} +12 -14
- package/src/tests/api_tests/security/form-logic-js-execution.test.ts +183 -0
- package/src/tests/api_tests/security/{F-0145-globalunique-leak.test.ts → globalunique-conflict-redaction.test.ts} +5 -6
- package/src/tests/api_tests/security/{F-0008-handle-incoming-communication-cross-tenant.test.ts → handle-incoming-communication-cross-tenant.test.ts} +8 -8
- package/src/tests/api_tests/security/{F-0007-invite-user-enumeration.test.ts → invite-user-enumeration.test.ts} +8 -8
- package/src/tests/api_tests/security/{F-0151-load-inbox-redaction.test.ts → load-inbox-redaction.test.ts} +5 -6
- package/src/tests/api_tests/security/{F-0016-prototype-pollution.test.ts → prototype-pollution.test.ts} +4 -4
- package/src/tests/api_tests/security/{F-0025-proxy-image-unauth-ssrf.test.ts → proxy-image-unauth-ssrf.test.ts} +11 -12
- package/src/tests/api_tests/security/{F-0013-sanitize-user-html-with-iframes.test.ts → sanitize-user-html-with-iframes.test.ts} +5 -5
- package/src/tests/api_tests/security/{F-0013-sanitize-user-html.test.ts → sanitize-user-html.test.ts} +4 -4
- package/src/tests/api_tests/security/schema-adminonly-enforcement.test.ts +287 -0
- package/src/tests/api_tests/security/{F-0076-self-admin-role-assignment.test.ts → self-admin-role-assignment.test.ts} +13 -13
- package/src/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.ts +162 -0
- package/src/tests/api_tests/security/{F-0015-webhook-outbound-ssrf.test.ts → webhook-outbound-ssrf.test.ts} +13 -14
- package/src/tests/api_tests/security/{F-0155-webhook-timeout.test.ts → webhook-timeout.test.ts} +6 -7
- package/src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts +279 -0
- package/src/tests/api_tests/webhook_error_handling.test.ts +1 -1
- package/src/tests/setup_isolated.ts +170 -0
- package/src/tests/tests.ts +60 -18
- package/src/tests/unit_tests/do_not_send_address.test.ts +60 -0
- package/src/tests/unit_tests/openapi_spec_freshness.test.ts +88 -0
- package/test_generated.pdf +0 -0
- package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +0 -301
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/sdk",
|
|
3
|
-
"version": "1.256.
|
|
3
|
+
"version": "1.256.15",
|
|
4
4
|
"description": "Code for interacting with the Tellescope API",
|
|
5
5
|
"main": "./lib/cjs/sdk.js",
|
|
6
6
|
"module": "./lib/esm/sdk.js",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/tellescope-os/tellescope#readme",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@tellescope/constants": "1.256.
|
|
36
|
-
"@tellescope/schema": "1.256.
|
|
37
|
-
"@tellescope/testing": "1.256.
|
|
38
|
-
"@tellescope/types-client": "1.256.
|
|
39
|
-
"@tellescope/types-models": "1.256.
|
|
40
|
-
"@tellescope/types-utilities": "1.256.
|
|
41
|
-
"@tellescope/utilities": "1.256.
|
|
42
|
-
"@tellescope/validation": "1.256.
|
|
35
|
+
"@tellescope/constants": "1.256.15",
|
|
36
|
+
"@tellescope/schema": "1.256.15",
|
|
37
|
+
"@tellescope/testing": "1.256.15",
|
|
38
|
+
"@tellescope/types-client": "1.256.15",
|
|
39
|
+
"@tellescope/types-models": "1.256.15",
|
|
40
|
+
"@tellescope/types-utilities": "1.256.15",
|
|
41
|
+
"@tellescope/utilities": "1.256.15",
|
|
42
|
+
"@tellescope/validation": "1.256.15",
|
|
43
43
|
"axios": "0.30.3",
|
|
44
44
|
"dotenv": "14.3.2",
|
|
45
45
|
"form-data": "4.0.4",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "180f82301125bdd5e73608991061e49ed3d41e96"
|
|
59
59
|
}
|
|
@@ -589,7 +589,7 @@ export const auto_merge_form_submission_tests = async ({ sdk, sdkNonAdmin }: { s
|
|
|
589
589
|
// Filling a BLANK phone is deliberately blocked too — contact-less patients are the primary
|
|
590
590
|
// takeover target, so "fill only if empty" would not close the hole. Staff update these fields.
|
|
591
591
|
// Previously this test asserted `phone === newPhone`; that behaviour was the vulnerability.
|
|
592
|
-
// See api_tests/security/public_endpoint_auth_field_lock.test.ts
|
|
592
|
+
// See api_tests/security/public_endpoint_auth_field_lock.test.ts.
|
|
593
593
|
&& !updatedDestination.phone
|
|
594
594
|
&& updatedDestination.email === testEmail
|
|
595
595
|
},
|
|
@@ -0,0 +1,358 @@
|
|
|
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
|
+
} from "../setup_isolated"
|
|
9
|
+
|
|
10
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
11
|
+
|
|
12
|
+
const RAND = () => Math.random().toString(36).slice(2, 10)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Coverage for auto-review of in-range vital readings — the `autoReview` flag on
|
|
16
|
+
* `vital_configurations.ranges`, applied inside `handle_vital_create` at the moment a reading is
|
|
17
|
+
* classified. For RPM-style programs most readings are in range, and clearing them one at a time
|
|
18
|
+
* out of the unreviewed-vitals queue is pure busywork; a range marked auto-review sets
|
|
19
|
+
* `reviewedAt` + `reviewedBy = SYSTEM_REVIEWER_ID` on the same update that writes classifications.
|
|
20
|
+
*
|
|
21
|
+
* The rule under test is deliberately strict: a reading gets one classification per MATCHING
|
|
22
|
+
* configuration, and auto-review happens only when EVERY one of those classifications came from an
|
|
23
|
+
* autoReview range. C is the check that pins this — a reading that is in-range under one
|
|
24
|
+
* configuration but caught by a non-auto-review range under another still needs a human, because
|
|
25
|
+
* the second configuration is the one expressing clinical concern.
|
|
26
|
+
*
|
|
27
|
+
* Vital configurations are business-wide (the handler reads them with `enduserId: { $exists: false }`
|
|
28
|
+
* over the caller's businessId), so this suite provisions its own throwaway tenant: without that it
|
|
29
|
+
* would classify — and auto-review — other concurrently running suites' readings, and theirs would
|
|
30
|
+
* bleed into these checks. It never calls `sdk.reset_db()`, which clears global throttle namespaces.
|
|
31
|
+
*
|
|
32
|
+
* Units come from the fixed VITAL_UNITS list, so checks that must not see each other's
|
|
33
|
+
* configurations are separated by unit rather than by name: SBP for A/B, DBP for the multi-config
|
|
34
|
+
* rule, A1C for the uncovered case.
|
|
35
|
+
*
|
|
36
|
+
* Classification is an asynchronous create side effect, so every read polls.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const poll_for = async <T,>(get: () => Promise<T>, done: (v: T) => boolean, attempts=20): Promise<T> => {
|
|
40
|
+
let value = await get()
|
|
41
|
+
for (let i = 0; i < attempts && !done(value); i++) {
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
43
|
+
value = await get()
|
|
44
|
+
}
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const enduser_observations_auto_review_checks = async (): Promise<Check[]> => {
|
|
49
|
+
const checks: Check[] = []
|
|
50
|
+
const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
|
|
51
|
+
|
|
52
|
+
let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
tenant = await setup_isolated_tenant({
|
|
56
|
+
name: `VitalAutoReview ${RAND()}`,
|
|
57
|
+
users: [{ isAdmin: true }],
|
|
58
|
+
apiKeys: 0,
|
|
59
|
+
})
|
|
60
|
+
const { sdk } = tenant
|
|
61
|
+
|
|
62
|
+
const enduser = await sdk.api.endusers.createOne({ fname: 'AutoReview', lname: 'Patient' })
|
|
63
|
+
|
|
64
|
+
const create_reading = (unit: string, value: number) => (
|
|
65
|
+
sdk.api.enduser_observations.createOne({
|
|
66
|
+
category: 'vital-signs',
|
|
67
|
+
status: 'final',
|
|
68
|
+
enduserId: enduser.id,
|
|
69
|
+
measurement: { unit, value },
|
|
70
|
+
timestamp: new Date(),
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
/* ===== A / B: one configuration, Target auto-reviews and High does not ===== */
|
|
75
|
+
|
|
76
|
+
await sdk.api.vital_configurations.createOne({
|
|
77
|
+
title: `AutoReview SBP ${RAND()}`,
|
|
78
|
+
unit: 'SBP',
|
|
79
|
+
ranges: [
|
|
80
|
+
{
|
|
81
|
+
classification: 'Target',
|
|
82
|
+
comparison: { type: 'Between', value: { lower: 100, upper: 200 } },
|
|
83
|
+
autoReview: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
classification: 'High',
|
|
87
|
+
comparison: { type: 'Greater Than', value: 200 },
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
/* ===== A: an in-range reading is reviewed automatically ===== */
|
|
93
|
+
|
|
94
|
+
const inRange = await create_reading('SBP', 150)
|
|
95
|
+
const inRangeAfter = await poll_for(
|
|
96
|
+
() => sdk.api.enduser_observations.getOne(inRange.id),
|
|
97
|
+
o => !!o.reviewedAt,
|
|
98
|
+
)
|
|
99
|
+
record(
|
|
100
|
+
'A1: a reading matching an auto-review range is marked reviewed',
|
|
101
|
+
!!inRangeAfter.reviewedAt,
|
|
102
|
+
`reviewedAt=${JSON.stringify(inRangeAfter.reviewedAt)} classifications=${JSON.stringify(inRangeAfter.classifications)}`
|
|
103
|
+
+ ` — must be set by handle_vital_create, not by an acknowledge call`,
|
|
104
|
+
)
|
|
105
|
+
record(
|
|
106
|
+
'A2: reviewedBy records the system sentinel, not a user id',
|
|
107
|
+
inRangeAfter.reviewedBy === SYSTEM_REVIEWER_ID,
|
|
108
|
+
`reviewedBy=${JSON.stringify(inRangeAfter.reviewedBy)} (want ${JSON.stringify(SYSTEM_REVIEWER_ID)})`,
|
|
109
|
+
)
|
|
110
|
+
record(
|
|
111
|
+
'A3: the auto-reviewed reading still carries its classification',
|
|
112
|
+
inRangeAfter.classifications?.length === 1 && inRangeAfter.classifications?.[0].classification === 'Target',
|
|
113
|
+
`classifications=${JSON.stringify(inRangeAfter.classifications)} (want exactly one, Target)`,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
/* ===== B: an out-of-range reading is classified but left for a human ===== */
|
|
117
|
+
|
|
118
|
+
const outOfRange = await create_reading('SBP', 250)
|
|
119
|
+
const outOfRangeAfter = await poll_for(
|
|
120
|
+
() => sdk.api.enduser_observations.getOne(outOfRange.id),
|
|
121
|
+
o => !!o.classifications?.length,
|
|
122
|
+
)
|
|
123
|
+
record(
|
|
124
|
+
'B1: an out-of-range reading is classified High',
|
|
125
|
+
outOfRangeAfter.classifications?.length === 1 && outOfRangeAfter.classifications?.[0].classification === 'High',
|
|
126
|
+
`classifications=${JSON.stringify(outOfRangeAfter.classifications)} (want exactly one, High)`,
|
|
127
|
+
)
|
|
128
|
+
record(
|
|
129
|
+
'B2: an out-of-range reading is NOT auto-reviewed',
|
|
130
|
+
!outOfRangeAfter.reviewedAt && !outOfRangeAfter.reviewedBy,
|
|
131
|
+
`reviewedAt=${JSON.stringify(outOfRangeAfter.reviewedAt)} reviewedBy=${JSON.stringify(outOfRangeAfter.reviewedBy)} (want neither set)`,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
/* ===== C: strict multi-config rule — one non-auto-review match blocks auto-review =====
|
|
135
|
+
* On its own unit (DBP) so these two configurations cannot affect A/B/D. */
|
|
136
|
+
|
|
137
|
+
await sdk.api.vital_configurations.createOne({
|
|
138
|
+
title: `AutoReview DBP target ${RAND()}`,
|
|
139
|
+
unit: 'DBP',
|
|
140
|
+
ranges: [
|
|
141
|
+
{
|
|
142
|
+
classification: 'Target',
|
|
143
|
+
comparison: { type: 'Between', value: { lower: 100, upper: 200 } },
|
|
144
|
+
autoReview: true,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
})
|
|
148
|
+
await sdk.api.vital_configurations.createOne({
|
|
149
|
+
title: `AutoReview DBP catchall ${RAND()}`,
|
|
150
|
+
unit: 'DBP',
|
|
151
|
+
// catch-all with autoReview off: this configuration always has an opinion, and it is never
|
|
152
|
+
// "clear this automatically"
|
|
153
|
+
ranges: [
|
|
154
|
+
{
|
|
155
|
+
classification: 'Low',
|
|
156
|
+
comparison: { type: 'Greater Than', value: 0 },
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const multiConfig = await create_reading('DBP', 150)
|
|
162
|
+
const multiConfigAfter = await poll_for(
|
|
163
|
+
() => sdk.api.enduser_observations.getOne(multiConfig.id),
|
|
164
|
+
o => (o.classifications?.length ?? 0) >= 2,
|
|
165
|
+
)
|
|
166
|
+
record(
|
|
167
|
+
'C1: a reading matched by two configurations gets one classification from each',
|
|
168
|
+
multiConfigAfter.classifications?.length === 2,
|
|
169
|
+
`classifications=${JSON.stringify(multiConfigAfter.classifications)} (want 2 — one per matching configuration)`,
|
|
170
|
+
)
|
|
171
|
+
record(
|
|
172
|
+
'C2: auto-review requires EVERY matched classification to come from an auto-review range',
|
|
173
|
+
!multiConfigAfter.reviewedAt && !multiConfigAfter.reviewedBy,
|
|
174
|
+
`reviewedAt=${JSON.stringify(multiConfigAfter.reviewedAt)} reviewedBy=${JSON.stringify(multiConfigAfter.reviewedBy)}`
|
|
175
|
+
+ ` — in range under one configuration, but the other's non-auto-review range also matched`,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
/* ===== D: a reading no configuration covers is never auto-reviewed ===== */
|
|
179
|
+
|
|
180
|
+
const uncovered = await create_reading('A1C', 150)
|
|
181
|
+
// nothing to wait for turning true, so poll for the negative to settle instead of reading once
|
|
182
|
+
const uncoveredAfter = await poll_for(
|
|
183
|
+
() => sdk.api.enduser_observations.getOne(uncovered.id),
|
|
184
|
+
o => !!o.reviewedAt || !!o.classifications?.length,
|
|
185
|
+
8,
|
|
186
|
+
)
|
|
187
|
+
record(
|
|
188
|
+
'D1: a reading on an uncovered unit gets no classifications and is not auto-reviewed',
|
|
189
|
+
!uncoveredAfter.classifications?.length && !uncoveredAfter.reviewedAt && !uncoveredAfter.reviewedBy,
|
|
190
|
+
`classifications=${JSON.stringify(uncoveredAfter.classifications)} reviewedAt=${JSON.stringify(uncoveredAfter.reviewedAt)}`
|
|
191
|
+
+ ` reviewedBy=${JSON.stringify(uncoveredAfter.reviewedBy)} (want all empty — no configuration covers A1C here)`,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
/* ===== E: a manual acknowledge still records the real reviewer ===== */
|
|
195
|
+
|
|
196
|
+
await sdk.api.enduser_observations.acknowledge({ ids: [outOfRange.id] })
|
|
197
|
+
const acknowledged = await sdk.api.enduser_observations.getOne(outOfRange.id)
|
|
198
|
+
record(
|
|
199
|
+
'E1: manual acknowledge still sets reviewedBy to the acting user',
|
|
200
|
+
!!acknowledged.reviewedAt && acknowledged.reviewedBy === tenant.users[0].id,
|
|
201
|
+
`reviewedAt=${JSON.stringify(acknowledged.reviewedAt)} reviewedBy=${JSON.stringify(acknowledged.reviewedBy)}`
|
|
202
|
+
+ ` (want the acting user ${tenant.users[0].id}, NOT the sentinel)`,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
/* ===== F/G/H: enduser-conditional auto-review =====
|
|
206
|
+
* A range's autoReviewConditions gate auto-review on the enduser: the range still classifies the
|
|
207
|
+
* reading, but only auto-reviews when the enduser satisfies the conditions. Separate endusers whose
|
|
208
|
+
* custom field `arField` matches vs. does not, on their own units so configs stay isolated. */
|
|
209
|
+
|
|
210
|
+
const matchingEnduser = await sdk.api.endusers.createOne({ fname: 'Match', lname: 'Patient', fields: { arField: 'yes' } })
|
|
211
|
+
const nonMatchingEnduser = await sdk.api.endusers.createOne({ fname: 'NoMatch', lname: 'Patient', fields: { arField: 'no' } })
|
|
212
|
+
|
|
213
|
+
const create_reading_for = (enduserId: string, unit: string, value: number) => (
|
|
214
|
+
sdk.api.enduser_observations.createOne({
|
|
215
|
+
category: 'vital-signs',
|
|
216
|
+
status: 'final',
|
|
217
|
+
enduserId,
|
|
218
|
+
measurement: { unit, value },
|
|
219
|
+
timestamp: new Date(),
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
/* ===== F: a Target range that auto-reviews only for endusers matching a custom-field condition ===== */
|
|
224
|
+
|
|
225
|
+
await sdk.api.vital_configurations.createOne({
|
|
226
|
+
title: `AutoReview BPM conditional ${RAND()}`,
|
|
227
|
+
unit: 'BPM',
|
|
228
|
+
ranges: [
|
|
229
|
+
{
|
|
230
|
+
classification: 'Target',
|
|
231
|
+
comparison: { type: 'Between', value: { lower: 60, upper: 100 } },
|
|
232
|
+
autoReview: true,
|
|
233
|
+
autoReviewConditions: { $and: [{ condition: { arField: 'yes' } }] },
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const condMatch = await create_reading_for(matchingEnduser.id, 'BPM', 80)
|
|
239
|
+
const condMatchAfter = await poll_for(
|
|
240
|
+
() => sdk.api.enduser_observations.getOne(condMatch.id),
|
|
241
|
+
o => !!o.reviewedAt || !!o.classifications?.length,
|
|
242
|
+
)
|
|
243
|
+
record(
|
|
244
|
+
'F1: a reading for an enduser matching the auto-review conditions IS auto-reviewed',
|
|
245
|
+
!!condMatchAfter.reviewedAt && condMatchAfter.reviewedBy === SYSTEM_REVIEWER_ID
|
|
246
|
+
&& condMatchAfter.classifications?.[0]?.classification === 'Target',
|
|
247
|
+
`reviewedAt=${JSON.stringify(condMatchAfter.reviewedAt)} reviewedBy=${JSON.stringify(condMatchAfter.reviewedBy)}`
|
|
248
|
+
+ ` classifications=${JSON.stringify(condMatchAfter.classifications)} (enduser arField=yes matches)`,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
const condNoMatch = await create_reading_for(nonMatchingEnduser.id, 'BPM', 80)
|
|
252
|
+
const condNoMatchAfter = await poll_for(
|
|
253
|
+
() => sdk.api.enduser_observations.getOne(condNoMatch.id),
|
|
254
|
+
o => !!o.classifications?.length,
|
|
255
|
+
)
|
|
256
|
+
record(
|
|
257
|
+
'F2: the same reading for an enduser NOT matching the conditions is classified but NOT auto-reviewed',
|
|
258
|
+
condNoMatchAfter.classifications?.[0]?.classification === 'Target'
|
|
259
|
+
&& !condNoMatchAfter.reviewedAt && !condNoMatchAfter.reviewedBy,
|
|
260
|
+
`reviewedAt=${JSON.stringify(condNoMatchAfter.reviewedAt)} reviewedBy=${JSON.stringify(condNoMatchAfter.reviewedBy)}`
|
|
261
|
+
+ ` classifications=${JSON.stringify(condNoMatchAfter.classifications)} (enduser arField=no does not match)`,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
/* ===== G: $or conditions — matching either branch auto-reviews ===== */
|
|
265
|
+
|
|
266
|
+
await sdk.api.vital_configurations.createOne({
|
|
267
|
+
title: `AutoReview O2 or-conditions ${RAND()}`,
|
|
268
|
+
unit: 'O2 Sat%',
|
|
269
|
+
ranges: [
|
|
270
|
+
{
|
|
271
|
+
classification: 'Target',
|
|
272
|
+
comparison: { type: 'Greater Than', value: 90 },
|
|
273
|
+
autoReview: true,
|
|
274
|
+
autoReviewConditions: { $or: [{ condition: { arField: 'other' } }, { condition: { arField: 'yes' } }] },
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const orMatch = await create_reading_for(matchingEnduser.id, 'O2 Sat%', 98)
|
|
280
|
+
const orMatchAfter = await poll_for(
|
|
281
|
+
() => sdk.api.enduser_observations.getOne(orMatch.id),
|
|
282
|
+
o => !!o.reviewedAt || !!o.classifications?.length,
|
|
283
|
+
)
|
|
284
|
+
record(
|
|
285
|
+
'G1: an enduser matching either $or branch is auto-reviewed',
|
|
286
|
+
!!orMatchAfter.reviewedAt && orMatchAfter.reviewedBy === SYSTEM_REVIEWER_ID,
|
|
287
|
+
`reviewedAt=${JSON.stringify(orMatchAfter.reviewedAt)} reviewedBy=${JSON.stringify(orMatchAfter.reviewedBy)}`
|
|
288
|
+
+ ` (arField=yes matches the second $or branch)`,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
/* ===== H: multi-config safety — a failing condition in one config blocks auto-review =====
|
|
292
|
+
* matchingEnduser (arField=yes) matches config-1's Target but NOT config-2's catch-all condition. */
|
|
293
|
+
|
|
294
|
+
await sdk.api.vital_configurations.createOne({
|
|
295
|
+
title: `AutoReview L/min target ${RAND()}`,
|
|
296
|
+
unit: 'L/min',
|
|
297
|
+
ranges: [
|
|
298
|
+
{
|
|
299
|
+
classification: 'Target',
|
|
300
|
+
comparison: { type: 'Between', value: { lower: 100, upper: 600 } },
|
|
301
|
+
autoReview: true,
|
|
302
|
+
autoReviewConditions: { $and: [{ condition: { arField: 'yes' } }] },
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
})
|
|
306
|
+
await sdk.api.vital_configurations.createOne({
|
|
307
|
+
title: `AutoReview L/min catchall ${RAND()}`,
|
|
308
|
+
unit: 'L/min',
|
|
309
|
+
ranges: [
|
|
310
|
+
{
|
|
311
|
+
classification: 'Low',
|
|
312
|
+
comparison: { type: 'Greater Than', value: 0 },
|
|
313
|
+
autoReview: true,
|
|
314
|
+
// enduser arField=yes, so this condition (arField=other) fails: config-2 is not auto-reviewable
|
|
315
|
+
autoReviewConditions: { $and: [{ condition: { arField: 'other' } }] },
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const condMultiConfig = await create_reading_for(matchingEnduser.id, 'L/min', 300)
|
|
321
|
+
const condMultiConfigAfter = await poll_for(
|
|
322
|
+
() => sdk.api.enduser_observations.getOne(condMultiConfig.id),
|
|
323
|
+
o => (o.classifications?.length ?? 0) >= 2,
|
|
324
|
+
)
|
|
325
|
+
record(
|
|
326
|
+
'H1: auto-review requires EVERY matched range to be auto-reviewable for this enduser',
|
|
327
|
+
condMultiConfigAfter.classifications?.length === 2
|
|
328
|
+
&& !condMultiConfigAfter.reviewedAt && !condMultiConfigAfter.reviewedBy,
|
|
329
|
+
`reviewedAt=${JSON.stringify(condMultiConfigAfter.reviewedAt)} reviewedBy=${JSON.stringify(condMultiConfigAfter.reviewedBy)}`
|
|
330
|
+
+ ` classifications=${JSON.stringify(condMultiConfigAfter.classifications)}`
|
|
331
|
+
+ ` — config-1 conditions match, config-2 conditions do not, so a human is still required`,
|
|
332
|
+
)
|
|
333
|
+
} finally {
|
|
334
|
+
// Teardown BEFORE anything asserts — assert calls process.exit, which would strand this tenant.
|
|
335
|
+
if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return checks
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Asserting wrapper, for standalone runs and for sequential use in the suite. */
|
|
342
|
+
export const enduser_observations_auto_review_tests = async () => {
|
|
343
|
+
assert_checks('Vital auto-review on create', await enduser_observations_auto_review_checks())
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Allow running this test file independently
|
|
347
|
+
if (require.main === module) {
|
|
348
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
349
|
+
enduser_observations_auto_review_tests()
|
|
350
|
+
.then(() => {
|
|
351
|
+
console.log("✅ Vital auto-review test suite completed successfully")
|
|
352
|
+
process.exit(0)
|
|
353
|
+
})
|
|
354
|
+
.catch((error) => {
|
|
355
|
+
console.error("❌ Vital auto-review test suite failed:", error)
|
|
356
|
+
process.exit(1)
|
|
357
|
+
})
|
|
358
|
+
}
|
|
@@ -207,7 +207,7 @@ export const integrations_redacted_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
207
207
|
}}
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
//
|
|
210
|
+
// connect_stripe must reject caller-supplied accountId (Stripe-account-spoofing defense).
|
|
211
211
|
// Stripe Connect is deprecated in Tellescope; accountId was a test shortcut, kept rejected to preserve SDK shape.
|
|
212
212
|
await async_test(
|
|
213
213
|
"connect_stripe rejects caller-supplied accountId",
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import axios from "axios"
|
|
4
|
+
|
|
5
|
+
import { Session } from "../../sdk"
|
|
6
|
+
import { wait } from "@tellescope/testing"
|
|
7
|
+
import {
|
|
8
|
+
assert_checks,
|
|
9
|
+
Check,
|
|
10
|
+
set_email_verification_code,
|
|
11
|
+
setup_isolated_tenant,
|
|
12
|
+
test_support_request,
|
|
13
|
+
} from "../setup_isolated"
|
|
14
|
+
|
|
15
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
16
|
+
|
|
17
|
+
const RAND = () => Math.random().toString(36).slice(2, 10)
|
|
18
|
+
|
|
19
|
+
// legacy shared fixture — teardown must refuse it
|
|
20
|
+
const SEEDED_TEST_BUSINESS_ID = '60398b1131a295e64f084ff6'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Covers the self-service organization signup chain, which had ZERO test coverage:
|
|
24
|
+
* `users.register` -> `users.submit_email_verification` -> `organizations.create_and_join`.
|
|
25
|
+
*
|
|
26
|
+
* That gap is why an earlier review nearly shipped a schema `adminOnly` flag on create_and_join that would have
|
|
27
|
+
* 401'd every new organization — a freshly registered user has no `roles`, because create_and_join is
|
|
28
|
+
* itself what grants Admin. This test is the regression guard for that.
|
|
29
|
+
*
|
|
30
|
+
* It also covers the new test-support provisioning module, including the assertion that matters most:
|
|
31
|
+
* teardown refuses any organization it did not create.
|
|
32
|
+
*
|
|
33
|
+
* Uses a `dontsend...@tellescope.com` address so the verification email is dropped at
|
|
34
|
+
* forCommunications before it is ever enqueued (locally the worker consumes the same test queue the
|
|
35
|
+
* API writes to, so an unsuppressed registration really does send mail).
|
|
36
|
+
*/
|
|
37
|
+
export const organization_signup_checks = async (): Promise<Check[]> => {
|
|
38
|
+
const checks: Check[] = []
|
|
39
|
+
const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
|
|
40
|
+
|
|
41
|
+
let bootstrapUserId = ''
|
|
42
|
+
let createdBusinessId = ''
|
|
43
|
+
let provisioned: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
/* ===== A: provisioning works with no seeded database ===== */
|
|
47
|
+
|
|
48
|
+
provisioned = await setup_isolated_tenant()
|
|
49
|
+
record(
|
|
50
|
+
'A1: provision-tenant returns a usable admin session',
|
|
51
|
+
!!provisioned.businessId && provisioned.sdk.userInfo?.businessId === provisioned.businessId,
|
|
52
|
+
`businessId=${provisioned.businessId} session businessId=${provisioned.sdk.userInfo?.businessId}`,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// proves the frozen utf8 api-key hash was reproduced correctly, and that the key's creator
|
|
56
|
+
// resolves — a key whose creator is missing fails authentication outright
|
|
57
|
+
let keyDetail = ''
|
|
58
|
+
let keyPass = false
|
|
59
|
+
try {
|
|
60
|
+
const keySession = new Session({ host, apiKey: provisioned.apiKeys[0] })
|
|
61
|
+
keyDetail = await keySession.test_authenticated()
|
|
62
|
+
keyPass = keyDetail === 'Authenticated!'
|
|
63
|
+
} catch (e: any) {
|
|
64
|
+
keyDetail = `rejected: ${e?.message || e?.toString()}`
|
|
65
|
+
}
|
|
66
|
+
record('A2: a provisioned API key authenticates', keyPass, `test_authenticated -> ${keyDetail}`)
|
|
67
|
+
|
|
68
|
+
// the tenant is genuinely usable, not just addressable
|
|
69
|
+
const enduser = await provisioned.sdk.api.endusers.createOne({ fname: 'Provisioned', lname: 'Patient' })
|
|
70
|
+
record('A3: the provisioned tenant accepts writes', !!enduser.id, `enduserId=${enduser.id}`)
|
|
71
|
+
|
|
72
|
+
/* ===== B: teardown refuses anything it did not create — the safety assertion ===== */
|
|
73
|
+
|
|
74
|
+
const refusal = await test_support_request('/teardown-tenant', { businessId: SEEDED_TEST_BUSINESS_ID })
|
|
75
|
+
record(
|
|
76
|
+
'B1: teardown REFUSES an organization without the provenance marker',
|
|
77
|
+
refusal.status === 403,
|
|
78
|
+
`status=${refusal.status} ${JSON.stringify(refusal.body)} (expected 403)`,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
/* ===== C: the signup chain ===== */
|
|
82
|
+
|
|
83
|
+
const email = `dontsend-signup-${RAND()}@tellescope.com`
|
|
84
|
+
const password = `Signup-${RAND()}-A1!`
|
|
85
|
+
|
|
86
|
+
const registerResult = await axios.request({
|
|
87
|
+
method: 'post', url: `${host}/v1/users/register`,
|
|
88
|
+
data: { email, password, termsVersion: '1' },
|
|
89
|
+
validateStatus: () => true,
|
|
90
|
+
})
|
|
91
|
+
record(
|
|
92
|
+
'C1: register accepts a new org-less user',
|
|
93
|
+
registerResult.status === 204,
|
|
94
|
+
`status=${registerResult.status} ${JSON.stringify(registerResult.data ?? '')}`,
|
|
95
|
+
)
|
|
96
|
+
await wait(undefined, 1200) // register is IP-limited to ~1/sec
|
|
97
|
+
|
|
98
|
+
// login works before verification, which is how a test learns the new user's id
|
|
99
|
+
const preVerifyLogin = await axios.request({
|
|
100
|
+
method: 'post', url: `${host}/v1/users/login`,
|
|
101
|
+
data: { email, password },
|
|
102
|
+
validateStatus: () => true,
|
|
103
|
+
})
|
|
104
|
+
bootstrapUserId = preVerifyLogin.data?.user?.id ?? ''
|
|
105
|
+
record(
|
|
106
|
+
'C2: the unverified user can log in (and is org-less)',
|
|
107
|
+
preVerifyLogin.status === 200 && !!bootstrapUserId && !preVerifyLogin.data?.user?.businessId,
|
|
108
|
+
`status=${preVerifyLogin.status} id=${bootstrapUserId} businessId=${preVerifyLogin.data?.user?.businessId ?? '(none)'}`,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// an unverified session must be rejected by a gated endpoint
|
|
112
|
+
const unverifiedAttempt = await axios.request({
|
|
113
|
+
method: 'post', url: `${host}/v1/organizations/create-and-join`,
|
|
114
|
+
data: { name: 'Should Not Work', subdomain: `nope-${RAND()}` },
|
|
115
|
+
headers: { Authorization: `Bearer ${preVerifyLogin.data?.authToken}` },
|
|
116
|
+
validateStatus: () => true,
|
|
117
|
+
})
|
|
118
|
+
record(
|
|
119
|
+
'C3: create_and_join is rejected before email verification',
|
|
120
|
+
unverifiedAttempt.status === 400,
|
|
121
|
+
`status=${unverifiedAttempt.status} ${JSON.stringify(unverifiedAttempt.data ?? '')}`,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
// set the pending code, then drive the REAL verification endpoint
|
|
125
|
+
const code = `verify-${RAND()}`
|
|
126
|
+
await set_email_verification_code(email, code)
|
|
127
|
+
|
|
128
|
+
const verified = await axios.request({
|
|
129
|
+
method: 'post', url: `${host}/v1/users/submit-email-verification`,
|
|
130
|
+
data: { email, code },
|
|
131
|
+
validateStatus: () => true,
|
|
132
|
+
})
|
|
133
|
+
record(
|
|
134
|
+
'C4: submit_email_verification accepts the code and mints a token',
|
|
135
|
+
verified.status === 200 && !!verified.data?.authToken,
|
|
136
|
+
`status=${verified.status} authToken=${!!verified.data?.authToken}`,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
/* ===== D: create_and_join — what that flag would have broken ===== */
|
|
140
|
+
|
|
141
|
+
const subdomain = `signup-${RAND()}`
|
|
142
|
+
const joined = await axios.request({
|
|
143
|
+
method: 'post', url: `${host}/v1/organizations/create-and-join`,
|
|
144
|
+
data: { name: `Signup Test ${RAND()}`, subdomain },
|
|
145
|
+
headers: { Authorization: `Bearer ${verified.data?.authToken}` },
|
|
146
|
+
validateStatus: () => true,
|
|
147
|
+
})
|
|
148
|
+
createdBusinessId = joined.data?.organization?.id ?? ''
|
|
149
|
+
record(
|
|
150
|
+
'D1: a freshly verified, org-less user can create and join an organization',
|
|
151
|
+
joined.status === 200 && !!createdBusinessId,
|
|
152
|
+
`status=${joined.status} organizationId=${createdBusinessId} ${joined.status !== 200 ? JSON.stringify(joined.data) : ''}`,
|
|
153
|
+
)
|
|
154
|
+
record(
|
|
155
|
+
'D2: the handler grants Admin and sets businessId (the roles bootstrap)',
|
|
156
|
+
joined.data?.user?.roles?.includes('Admin') && joined.data?.user?.businessId === createdBusinessId,
|
|
157
|
+
`roles=${JSON.stringify(joined.data?.user?.roles)} businessId=${joined.data?.user?.businessId}`,
|
|
158
|
+
)
|
|
159
|
+
record(
|
|
160
|
+
'D3: a fresh token is returned',
|
|
161
|
+
!!joined.data?.authToken && joined.data.authToken !== verified.data?.authToken,
|
|
162
|
+
`token returned=${!!joined.data?.authToken}, rotated=${joined.data?.authToken !== verified.data?.authToken}`,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const second = await axios.request({
|
|
166
|
+
method: 'post', url: `${host}/v1/organizations/create-and-join`,
|
|
167
|
+
data: { name: 'Second Org', subdomain: `signup-${RAND()}` },
|
|
168
|
+
headers: { Authorization: `Bearer ${joined.data?.authToken}` },
|
|
169
|
+
validateStatus: () => true,
|
|
170
|
+
})
|
|
171
|
+
record(
|
|
172
|
+
'D4: a user already in an organization cannot create another',
|
|
173
|
+
second.status === 400,
|
|
174
|
+
`status=${second.status} ${JSON.stringify(second.data ?? '')}`,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
/* ===== E: teardown actually removes the tenant ===== */
|
|
178
|
+
|
|
179
|
+
// a second, disposable tenant so the assertions above keep their fixture
|
|
180
|
+
const disposable = await setup_isolated_tenant({ users: [{ isAdmin: true }], apiKeys: 1 })
|
|
181
|
+
const disposableKey = disposable.apiKeys[0]
|
|
182
|
+
await disposable.sdk.api.endusers.createOne({ fname: 'Disposable', lname: 'Patient' })
|
|
183
|
+
await disposable.teardown()
|
|
184
|
+
|
|
185
|
+
const secondTeardown = await test_support_request('/teardown-tenant', { businessId: disposable.businessId })
|
|
186
|
+
record(
|
|
187
|
+
'E1: teardown removes the organization record itself',
|
|
188
|
+
secondTeardown.status === 404,
|
|
189
|
+
`re-teardown status=${secondTeardown.status} ${JSON.stringify(secondTeardown.body)} (expected 404 — org gone)`,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
let revokedDetail = ''
|
|
193
|
+
let revokedPass = false
|
|
194
|
+
try {
|
|
195
|
+
revokedDetail = await (new Session({ host, apiKey: disposableKey })).test_authenticated()
|
|
196
|
+
} catch (e: any) {
|
|
197
|
+
revokedPass = true
|
|
198
|
+
revokedDetail = `rejected: ${e?.message || e?.toString()}`
|
|
199
|
+
}
|
|
200
|
+
record(
|
|
201
|
+
'E2: the torn-down tenant\'s API key no longer authenticates',
|
|
202
|
+
revokedPass,
|
|
203
|
+
revokedDetail,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
} finally {
|
|
207
|
+
if (provisioned) { try { await provisioned.teardown() } catch (e) { console.error('teardown failed', e) } }
|
|
208
|
+
// The signup-created org is NOT test-seeded, so teardown-tenant correctly refuses it; the org row
|
|
209
|
+
// is documented residue. The bootstrap user IS removable via test-support (the
|
|
210
|
+
// /tellescope-admin/delete-user route needs a staff session an isolated test deliberately lacks —
|
|
211
|
+
// posting to it without an Authorization header silently 401'd, so this used to leak every run).
|
|
212
|
+
if (bootstrapUserId) {
|
|
213
|
+
try { await test_support_request('/delete-user', { userId: bootstrapUserId }) } catch {}
|
|
214
|
+
}
|
|
215
|
+
if (createdBusinessId) {
|
|
216
|
+
console.log(` note: organization ${createdBusinessId} was created by the signup flow and is left behind (nothing can delete an org record)`)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return checks
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Asserting wrapper, for standalone runs and for sequential use in the suite. */
|
|
224
|
+
export const organization_signup_tests = async () => {
|
|
225
|
+
assert_checks('Organization signup', await organization_signup_checks())
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Allow running this test file independently
|
|
229
|
+
if (require.main === module) {
|
|
230
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
231
|
+
organization_signup_tests()
|
|
232
|
+
.then(() => {
|
|
233
|
+
console.log("✅ Organization signup test suite completed successfully")
|
|
234
|
+
process.exit(0)
|
|
235
|
+
})
|
|
236
|
+
.catch((error) => {
|
|
237
|
+
console.error("❌ Organization signup test suite failed:", error)
|
|
238
|
+
process.exit(1)
|
|
239
|
+
})
|
|
240
|
+
}
|