@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.
- package/lib/cjs/sdk.d.ts +2 -2
- package/lib/cjs/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js +472 -0
- package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js +336 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.js +205 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +236 -203
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/sdk.d.ts +2 -2
- package/lib/esm/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js +465 -0
- package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js +332 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.js +201 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +236 -203
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +301 -0
- package/src/tests/api_tests/security/suggested-reply-redaction.test.ts +232 -0
- package/src/tests/api_tests/stedi_approval.test.ts +167 -0
- package/src/tests/tests.ts +34 -13
- 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
|
+
}
|
package/src/tests/tests.ts
CHANGED
|
@@ -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
|
|
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 })
|
package/test_generated.pdf
CHANGED
|
Binary file
|