@tellescope/sdk 1.256.9 → 1.256.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/cjs/sdk.d.ts +2 -2
  2. package/lib/cjs/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
  4. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
  5. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js +472 -0
  6. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
  7. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
  8. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
  9. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js +336 -0
  10. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
  11. package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts +6 -0
  12. package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
  13. package/lib/cjs/tests/api_tests/stedi_approval.test.js +205 -0
  14. package/lib/cjs/tests/api_tests/stedi_approval.test.js.map +1 -0
  15. package/lib/cjs/tests/tests.d.ts.map +1 -1
  16. package/lib/cjs/tests/tests.js +236 -203
  17. package/lib/cjs/tests/tests.js.map +1 -1
  18. package/lib/esm/sdk.d.ts +2 -2
  19. package/lib/esm/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
  20. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
  21. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
  22. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js +465 -0
  23. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
  24. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
  25. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
  26. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js +332 -0
  27. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
  28. package/lib/esm/tests/api_tests/stedi_approval.test.d.ts +6 -0
  29. package/lib/esm/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
  30. package/lib/esm/tests/api_tests/stedi_approval.test.js +201 -0
  31. package/lib/esm/tests/api_tests/stedi_approval.test.js.map +1 -0
  32. package/lib/esm/tests/tests.d.ts.map +1 -1
  33. package/lib/esm/tests/tests.js +236 -203
  34. package/lib/esm/tests/tests.js.map +1 -1
  35. package/lib/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +10 -10
  37. package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +301 -0
  38. package/src/tests/api_tests/security/suggested-reply-redaction.test.ts +232 -0
  39. package/src/tests/api_tests/stedi_approval.test.ts +167 -0
  40. package/src/tests/tests.ts +34 -13
  41. package/test_generated.pdf +0 -0
@@ -0,0 +1,167 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session, EnduserSession } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ log_header,
7
+ } from "@tellescope/testing"
8
+ import { STEDI_TITLE } from "@tellescope/constants"
9
+ import { authenticate_enduser_via_token, setup_tests } from "../setup"
10
+
11
+ const host = process.env.API_URL || 'http://localhost:8080' as const
12
+
13
+ // Stedi runs on Tellescope's own platform credentials (zz_secrets) and each eligibility check costs
14
+ // Tellescope $0.10, so an organization must be approved by a Tellescope super admin
15
+ // (Organization.stediAllowed) before any Stedi call is made on its behalf.
16
+ //
17
+ // Every assertion here is a REJECTION assertion, so nothing in this file ever reaches Stedi.
18
+ export const stedi_approval_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
19
+ log_header("Stedi Pre-Approval (stediAllowed) Tests")
20
+
21
+ const orgId = sdk.userInfo.businessId
22
+ const businessId = orgId
23
+
24
+ // A. Super-admin write gate.
25
+ //
26
+ // `isa` is only minted by GET /switch-tenant, so this plainly-authenticated admin session never
27
+ // has it — even when TEST_EMAIL happens to be in the API's SUPER_ADMINS allowlist.
28
+ log_header("A. stediAllowed is writable by super admins only")
29
+
30
+ await async_test(
31
+ "org admin cannot enable stediAllowed for their own organization",
32
+ () => sdk.api.organizations.updateOne(orgId, { stediAllowed: true }),
33
+ { shouldError: true, onError: (e: { message: string }) => /not allowed/i.test(e.message) }
34
+ )
35
+
36
+ // Regression test for the truthiness hole in the shared super-admin relationship constraint:
37
+ // it used to test `options.updates?.stediAllowed` (falsy for `false`), so an org admin could
38
+ // clear super-admin-owned flags even though they could never set them.
39
+ await async_test(
40
+ "org admin cannot disable stediAllowed either (guard is key-presence, not truthiness)",
41
+ () => sdk.api.organizations.updateOne(orgId, { stediAllowed: false }),
42
+ { shouldError: true, onError: (e: { message: string }) => /not allowed/i.test(e.message) }
43
+ )
44
+
45
+ // Same bug, pre-existing field — proves the fix wasn't scoped to the new flag only.
46
+ await async_test(
47
+ "org admin cannot disable bedrockAIAllowed",
48
+ () => sdk.api.organizations.updateOne(orgId, { bedrockAIAllowed: false }),
49
+ { shouldError: true, onError: (e: { message: string }) => /not allowed/i.test(e.message) }
50
+ )
51
+
52
+ // ...and that it covers the whole gated list, not just the two booleans.
53
+ await async_test(
54
+ "org admin cannot disable allowCreateSuborganizations",
55
+ () => sdk.api.organizations.updateOne(orgId, { allowCreateSuborganizations: false }),
56
+ { shouldError: true, onError: (e: { message: string }) => /not allowed/i.test(e.message) }
57
+ )
58
+
59
+ // B. An unapproved organization is rejected at every Stedi entry point.
60
+ log_header("B. Unapproved organizations cannot reach Stedi")
61
+
62
+ const organization = await sdk.api.organizations.getOne(orgId)
63
+ if (organization.stediAllowed) {
64
+ console.log(
65
+ "⚠️ SKIPPING section B: the test organization already has stediAllowed=true "
66
+ + "(hand-toggled in this dev database). Turn it off in the super admin console and re-run — "
67
+ + "these assertions would otherwise report a false pass."
68
+ )
69
+ return
70
+ }
71
+
72
+ const enduser = await sdk.api.endusers.createOne({
73
+ email: `stedi-approval-${Date.now()}@test.tellescope.com`,
74
+ fname: 'Stedi',
75
+ lname: 'Approval',
76
+ })
77
+
78
+ try {
79
+ await async_test(
80
+ "staff: Stedi payer search (proxy_read) is rejected",
81
+ () => sdk.api.integrations.proxy_read({ integration: STEDI_TITLE, type: 'payers', query: 'aetna' }),
82
+ { shouldError: true, onError: (e: { message: string }) => /not enabled for this organization/i.test(e.message) }
83
+ )
84
+
85
+ await async_test(
86
+ "staff: Stedi payer importer (load_payers) is rejected",
87
+ () => sdk.api.integrations.load_payers({ integration: STEDI_TITLE, query: 'aetna' }),
88
+ { shouldError: true, onError: (e: { message: string }) => /not enabled for this organization/i.test(e.message) }
89
+ )
90
+
91
+ await async_test(
92
+ "staff: Stedi eligibility check is rejected on approval, before the credit checks",
93
+ () => sdk.api.integrations.proxy_read({
94
+ integration: STEDI_TITLE,
95
+ type: 'stedi-eligibility',
96
+ id: enduser.id,
97
+ query: JSON.stringify({ serviceCode: '30', npi: '1234567890' }),
98
+ }),
99
+ {
100
+ shouldError: true,
101
+ // The absence of /credit/ is the real assertion: it proves the approval gate fires ahead of
102
+ // the credit gate, so an unapproved org is never told to go buy credits.
103
+ onError: (e: { message: string }) => (
104
+ /not enabled for this organization/i.test(e.message) && !/credit/i.test(e.message)
105
+ ),
106
+ }
107
+ )
108
+
109
+ // Enduser sessions reach both branches for real: StediEligibilityInput auto-runs the check, and
110
+ // InsuranceInput search-on-types against the payer branch. Approval/billing state must not leak
111
+ // to patients.
112
+ const enduserSdk = new EnduserSession({ host, businessId })
113
+ await authenticate_enduser_via_token(sdk, enduserSdk, { id: enduser.id })
114
+
115
+ // Exact-match on the generic copy: no "Stedi", no "credits", no "contact sales", no mention that
116
+ // approval is the reason. Note this is deliberately the SAME string the enduser-facing credit
117
+ // rejection uses, so it cannot prove which gate fired — the staff assertion above is what pins
118
+ // down the approval-before-credits ordering.
119
+ const messageIsGenericForEnduser = (e: { message: string }) => (
120
+ e.message === 'Eligibility check is currently unavailable'
121
+ )
122
+
123
+ await async_test(
124
+ "enduser: eligibility check rejected with a generic message (no approval/billing leak)",
125
+ () => enduserSdk.api.integrations.proxy_read({
126
+ integration: STEDI_TITLE,
127
+ type: 'stedi-eligibility',
128
+ query: JSON.stringify({ serviceCode: '30', npi: '1234567890' }),
129
+ }),
130
+ { shouldError: true, onError: messageIsGenericForEnduser }
131
+ )
132
+
133
+ await async_test(
134
+ "enduser: payer search rejected with a generic message (no approval/billing leak)",
135
+ () => enduserSdk.api.integrations.proxy_read({
136
+ integration: STEDI_TITLE,
137
+ type: 'payers',
138
+ query: 'aetna',
139
+ }),
140
+ { shouldError: true, onError: messageIsGenericForEnduser }
141
+ )
142
+ } finally {
143
+ await sdk.api.endusers.deleteOne(enduser.id).catch(console.error)
144
+ }
145
+ }
146
+
147
+ // Allow running this test file independently
148
+ if (require.main === module) {
149
+ console.log(`Using API URL: ${host}`)
150
+ const sdk = new Session({ host })
151
+ const sdkNonAdmin = new Session({ host })
152
+
153
+ const runTests = async () => {
154
+ await setup_tests(sdk, sdkNonAdmin)
155
+ await stedi_approval_tests({ sdk, sdkNonAdmin })
156
+ }
157
+
158
+ runTests()
159
+ .then(() => {
160
+ console.log("✅ Stedi pre-approval tests completed successfully")
161
+ process.exit(0)
162
+ })
163
+ .catch((error) => {
164
+ console.error("❌ Stedi pre-approval tests failed:", error)
165
+ process.exit(1)
166
+ })
167
+ }
@@ -125,6 +125,7 @@ import { prototype_pollution_tests } from "./api_tests/security/F-0016-prototype
125
125
  import { enduser_cross_create_tests } from "./api_tests/security/F-0116-F-0119-enduser-cross-create.test";
126
126
  import { globalunique_leak_tests } from "./api_tests/security/F-0145-globalunique-leak.test";
127
127
  import { load_inbox_redaction_tests } from "./api_tests/security/F-0151-load-inbox-redaction.test";
128
+ import { suggested_reply_redaction_tests } from "./api_tests/security/suggested-reply-redaction.test";
128
129
  import { webhook_timeout_tests } from "./api_tests/security/F-0155-webhook-timeout.test";
129
130
  import { enduser_ai_summary_trust_gate_tests } from "./api_tests/security/enduser-ai-summary-trust-gate.test";
130
131
  import { allowed_paths_confinement_tests } from "./api_tests/security/F-0166-allowed-paths-wildcard-bypass.test";
@@ -151,6 +152,7 @@ import { conditional_logic_medication_unit_tests } from "./unit_tests/conditiona
151
152
  import { phone_validation_unit_tests } from "./unit_tests/phone_validation.test";
152
153
  import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
153
154
  import { organization_settings_duplicates_tests } from "./api_tests/organization_settings_duplicates.test";
155
+ import { stedi_approval_tests } from "./api_tests/stedi_approval.test";
154
156
  import { calendar_events_bulk_update_tests } from "./api_tests/calendar_events_bulk_update.test";
155
157
  import { calendar_event_references_update_tests } from "./api_tests/calendar_event_references_update.test";
156
158
  import { openloop_webhooks_tests } from "./api_tests/openloop_webhooks.test";
@@ -11242,21 +11244,38 @@ export const cancel_upcoming_appointments_journey_action_test = async () => {
11242
11244
  action: { type: 'cancelFutureAppointments', info: {} },
11243
11245
  })
11244
11246
 
11247
+ // the worker processes automated actions on a poll (every 8s per thread, see worker/app.js pollingDelaySeconds),
11248
+ // so wait for the action itself to reach a terminal status before checking its effects.
11249
+ // shouldError=true so that a timeout returns the last result, letting us report status/errorMessage
11250
+ const processedActions = await pollForResults(
11251
+ () => sdk.api.automated_actions.getSome({ filter: { enduserId: enduser.id } }),
11252
+ as => !!as.find(a => a.status !== 'active'),
11253
+ 500,
11254
+ 40,
11255
+ true,
11256
+ )
11257
+ const processed = (processedActions as ClientModelForName['automated_actions'][])?.find(a => a.status !== 'active')
11258
+ assert(
11259
+ processed?.status === 'finished',
11260
+ `cancelFutureAppointments action did not finish: status=${processed?.status ?? 'active (timed out)'}, error=${processed?.errorMessage ?? 'none'}`,
11261
+ 'cancelFutureAppointments action processed',
11262
+ )
11263
+
11245
11264
  await async_test(
11246
- `Upcoming event is cancelled, past appointment is not`,
11247
- () => pollForResults(
11248
- sdk.api.calendar_events.getSome,
11249
- es => (
11250
- !!es.find(e => e.id === e1.id && !e.cancelledAt) // past event should not be cancelled
11251
- && !!es.find(e => e.id === e2.id && !!e.cancelledAt) // future event should be cancelled
11252
- && !!es.find(e => e.id === e3.id && !e.cancelledAt) // past event not enduser should not be cancelled
11253
- && !!es.find(e => e.id === e4.id && !e.cancelledAt) // future event not enduser should not be cancelled
11254
- ),
11255
- 50,
11256
- 100,
11257
- ),
11265
+ `Upcoming event is cancelled`,
11266
+ () => pollForResults(() => sdk.api.calendar_events.getOne(e2.id), e => !!e.cancelledAt, 250, 40),
11258
11267
  passOnAnyResult
11259
- )
11268
+ )
11269
+
11270
+ // a wrongly-cancelled event is a regression to surface immediately, not a state to wait on
11271
+ for (const { event, label } of [
11272
+ { event: e1, label: 'past event' },
11273
+ { event: e3, label: 'non-enduser event 1' },
11274
+ { event: e4, label: 'non-enduser event 2' },
11275
+ ]) {
11276
+ const fetched = await sdk.api.calendar_events.getOne(event.id)
11277
+ assert(!fetched.cancelledAt, `${label} (${fetched.title}) was cancelled but should not be`, `${label} not cancelled`)
11278
+ }
11260
11279
 
11261
11280
  await Promise.all([
11262
11281
  sdk.api.endusers.deleteOne(enduser.id),
@@ -15368,6 +15387,7 @@ const ip_address_form_tests = async () => {
15368
15387
  await enduser_cross_create_tests({ sdk, sdkNonAdmin })
15369
15388
  await globalunique_leak_tests({ sdk, sdkNonAdmin })
15370
15389
  await load_inbox_redaction_tests({ sdk, sdkNonAdmin })
15390
+ await suggested_reply_redaction_tests({ sdk, sdkNonAdmin })
15371
15391
  await webhook_timeout_tests({ sdk, sdkNonAdmin })
15372
15392
  await public_endpoint_auth_field_lock_tests({ sdk, sdkNonAdmin })
15373
15393
  await formsort_webhook_auth_field_lock_tests({ sdk, sdkNonAdmin })
@@ -15397,6 +15417,7 @@ const ip_address_form_tests = async () => {
15397
15417
  await organization_api_keys_tests({ sdk, sdkNonAdmin })
15398
15418
  await scoped_api_keys_tests({ sdk, sdkNonAdmin })
15399
15419
  await organization_settings_duplicates_tests({ sdk, sdkNonAdmin })
15420
+ await stedi_approval_tests({ sdk, sdkNonAdmin })
15400
15421
  await enduser_session_invalidation_tests({ sdk, sdkNonAdmin })
15401
15422
  await chats_analytics_tests({ sdk, sdkNonAdmin })
15402
15423
  await field_redaction_tests({ sdk, sdkNonAdmin })
Binary file