@tellescope/sdk 1.252.2 → 1.253.0
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/integrations_redacted.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js +10 -4
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/mdi_webhooks.test.d.ts +3 -4
- package/lib/cjs/tests/api_tests/mdi_webhooks.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/mdi_webhooks.test.js +3 -4
- package/lib/cjs/tests/api_tests/mdi_webhooks.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js +108 -62
- package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +177 -0
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +131 -127
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/integrations_redacted.test.js +10 -4
- package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/esm/tests/api_tests/mdi_webhooks.test.d.ts +3 -4
- package/lib/esm/tests/api_tests/mdi_webhooks.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/mdi_webhooks.test.js +3 -4
- package/lib/esm/tests/api_tests/mdi_webhooks.test.js.map +1 -1
- package/lib/esm/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/no_access_permission_checks.test.js +108 -62
- package/lib/esm/tests/api_tests/no_access_permission_checks.test.js.map +1 -1
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +177 -0
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +131 -127
- 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/integrations_redacted.test.ts +5 -3
- package/src/tests/api_tests/mdi_webhooks.test.ts +99 -0
- package/src/tests/api_tests/no_access_permission_checks.test.ts +38 -0
- package/src/tests/api_tests/set_fields_order_templates.test.ts +122 -0
- package/src/tests/tests.ts +2 -0
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
assert,
|
|
6
|
+
log_header,
|
|
7
|
+
} from "@tellescope/testing"
|
|
8
|
+
import { setup_tests } from "../setup"
|
|
9
|
+
|
|
10
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
11
|
+
|
|
12
|
+
const mdiUrl = `${host}/v1/webhooks/mdi`
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* POST a JSON payload to the MDI webhook endpoint with optional auth/signature headers.
|
|
16
|
+
*/
|
|
17
|
+
const postMDI = async (body: object, headers: Record<string, string> = {}) => {
|
|
18
|
+
const res = await fetch(mdiUrl, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
21
|
+
body: JSON.stringify(body),
|
|
22
|
+
})
|
|
23
|
+
let data: any
|
|
24
|
+
try { data = await res.text() } catch { data = null }
|
|
25
|
+
return { status: res.status, data }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* MD Integrations Webhooks Tests
|
|
30
|
+
*
|
|
31
|
+
* The inbound endpoint is security-first: it authenticates the tenant via the
|
|
32
|
+
* static Authorization token BEFORE any DB write or side effect. These tests
|
|
33
|
+
* assert those rejection paths, which don't require a configured MDI integration.
|
|
34
|
+
*
|
|
35
|
+
* NOTE: a full event round-trip (case_approved -> mdiStatus, offering_submitted
|
|
36
|
+
* -> enduser_medications, message_created -> chat, etc.) requires an MDI
|
|
37
|
+
* integration row carrying the Authorization token in webhooksSecret.
|
|
38
|
+
* That row can only be created through the connect flow against valid MDI
|
|
39
|
+
* Sandbox credentials (add_api_key_integration validates client creds via a live
|
|
40
|
+
* MDI API call), so the end-to-end path is exercised in the Sandbox per the task
|
|
41
|
+
* plan rather than here.
|
|
42
|
+
*/
|
|
43
|
+
export const mdi_webhooks_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
|
|
44
|
+
log_header("MD Integrations Webhooks Tests")
|
|
45
|
+
|
|
46
|
+
// 1. Missing Authorization header -> 401 (tenant cannot be identified)
|
|
47
|
+
{
|
|
48
|
+
const { status } = await postMDI({ event_type: 'case_approved', case_id: 'mdi-case-test-1' })
|
|
49
|
+
assert(status === 401, `missing Authorization should 401 (got ${status})`, "MDI webhook rejects missing Authorization")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Bogus Authorization token (no matching integration) -> 401
|
|
53
|
+
{
|
|
54
|
+
const { status } = await postMDI(
|
|
55
|
+
{ event_type: 'case_approved', case_id: 'mdi-case-test-1' },
|
|
56
|
+
{ 'Authorization': `Bearer not-a-real-mdi-token-${Date.now()}` },
|
|
57
|
+
)
|
|
58
|
+
assert(status === 401, `unknown Authorization should 401 (got ${status})`, "MDI webhook rejects unknown Authorization")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3. Missing event_type -> 400 (bad request), still before any tenant work
|
|
62
|
+
{
|
|
63
|
+
const { status } = await postMDI(
|
|
64
|
+
{ case_id: 'mdi-case-test-1' } as any,
|
|
65
|
+
{ 'Authorization': `Bearer not-a-real-mdi-token-${Date.now()}` },
|
|
66
|
+
)
|
|
67
|
+
assert(status === 400 || status === 401, `missing event_type should 400/401 (got ${status})`, "MDI webhook rejects payload without event_type")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 4. message_created with bogus auth -> still 401 (auth precedes patient lookup)
|
|
71
|
+
{
|
|
72
|
+
const { status } = await postMDI(
|
|
73
|
+
{ event_type: 'message_created', patient_id: 'mdi-patient-x', message_id: 'm-1', user_type: 'App\\Models\\User' },
|
|
74
|
+
{ 'Authorization': `Bearer not-a-real-mdi-token-${Date.now()}` },
|
|
75
|
+
)
|
|
76
|
+
assert(status === 401, `message_created with bad auth should 401 (got ${status})`, "MDI webhook authenticates before patient lookup")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Allow running this test file independently
|
|
81
|
+
if (require.main === module) {
|
|
82
|
+
const sdk = new Session({ host })
|
|
83
|
+
const sdkNonAdmin = new Session({ host })
|
|
84
|
+
|
|
85
|
+
const runTests = async () => {
|
|
86
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
87
|
+
await mdi_webhooks_tests({ sdk, sdkNonAdmin })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
runTests()
|
|
91
|
+
.then(() => {
|
|
92
|
+
console.log("✅ MD Integrations webhooks test suite completed successfully")
|
|
93
|
+
process.exit(0)
|
|
94
|
+
})
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
console.error("❌ MD Integrations webhooks test suite failed:", error)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
@@ -80,6 +80,44 @@ export const no_access_permission_checks_tests = async ({ sdk, sdkNonAdmin } : {
|
|
|
80
80
|
const originalRoles = sdkNonAdmin.userInfo.roles
|
|
81
81
|
|
|
82
82
|
try {
|
|
83
|
+
// ========================================
|
|
84
|
+
// Test 0: Healthie proxy_read endpoints are admin-only
|
|
85
|
+
// ========================================
|
|
86
|
+
// These run before any role manipulation, while sdkNonAdmin has its default non-admin role.
|
|
87
|
+
// The admin check is inline with the Healthie dispatch branches (after integration resolution),
|
|
88
|
+
// so a throwaway Healthie integration record must exist for requests to reach it.
|
|
89
|
+
log_header("Test 0: Healthie proxy_read endpoints reject non-admins")
|
|
90
|
+
|
|
91
|
+
const healthieIntegration = await sdk.api.integrations.createOne({
|
|
92
|
+
title: 'Healthie',
|
|
93
|
+
authentication: { type: 'apiKey', info: { access_token: 'dummy-healthie-key-for-admin-only-test', refresh_token: 'unused', scope: '', token_type: 'Bearer', expiry_date: new Date().getTime() } },
|
|
94
|
+
})
|
|
95
|
+
try {
|
|
96
|
+
await async_test(
|
|
97
|
+
"proxy_read healthie patients - should block non-admin user",
|
|
98
|
+
() => sdkNonAdmin.api.integrations.proxy_read({ integration: 'Healthie', type: 'patients' }),
|
|
99
|
+
{ shouldError: true, onError: (e: any) => e.message === "Admin access required" }
|
|
100
|
+
)
|
|
101
|
+
await async_test(
|
|
102
|
+
"proxy_read healthie appointment-types - should block non-admin user",
|
|
103
|
+
() => sdkNonAdmin.api.integrations.proxy_read({ integration: 'Healthie', type: 'appointment-types' }),
|
|
104
|
+
{ shouldError: true, onError: (e: any) => e.message === "Admin access required" }
|
|
105
|
+
)
|
|
106
|
+
// Admin is not blocked by the role check (the dummy credentials fail upstream in Healthie's API,
|
|
107
|
+
// but never with the 403 role rejection)
|
|
108
|
+
try {
|
|
109
|
+
await sdk.api.integrations.proxy_read({ integration: 'Healthie', type: 'patients', query: JSON.stringify({ pageSize: 1 }) })
|
|
110
|
+
console.log("✅ proxy_read healthie patients - admin allowed")
|
|
111
|
+
} catch (e: any) {
|
|
112
|
+
if (e.message === "Admin access required") {
|
|
113
|
+
throw new Error("proxy_read healthie patients - admin was incorrectly blocked by the role check")
|
|
114
|
+
}
|
|
115
|
+
console.log("✅ proxy_read healthie patients - admin passes role check (dummy Healthie credentials rejected upstream)")
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
await sdk.api.integrations.deleteOne(healthieIntegration.id)
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
// Assign the restricted role to non-admin user
|
|
84
122
|
await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: [noAccessTestRole] }, { replaceObjectFields: true })
|
|
85
123
|
await wait(undefined, 1500) // wait for role change to propagate
|
|
@@ -228,12 +228,134 @@ const beluga_webhook_block = async ({ sdk }: { sdk: Session }) => {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// Block C: Beluga tracking status update events (PACKAGE_*)
|
|
232
|
+
const beluga_tracking_status_block = async ({ sdk }: { sdk: Session }) => {
|
|
233
|
+
log_header("Block C: Beluga tracking status updates (PACKAGE_* events)")
|
|
234
|
+
|
|
235
|
+
const webhookUrl = `${host}/v1/webhooks/beluga`
|
|
236
|
+
const externalOrderId = `EXT-C-${Date.now()}`
|
|
237
|
+
const trackingUrl = 'https://tools.usps.com/go/TrackConfirmAction?tLabels=9400-CCC'
|
|
238
|
+
|
|
239
|
+
const enduser = await sdk.api.endusers.createOne({})
|
|
240
|
+
const form = await sdk.api.forms.createOne({ title: 'Tracking Status Beluga Form' })
|
|
241
|
+
const formResponse = await sdk.api.form_responses.createOne({
|
|
242
|
+
formId: form.id,
|
|
243
|
+
enduserId: enduser.id,
|
|
244
|
+
formTitle: form.title,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const inTransitTrigger = await sdk.api.automation_triggers.createOne({
|
|
248
|
+
title: `${TRIGGER_TITLE_BLOCK_B} (In Transit)`,
|
|
249
|
+
status: 'Active',
|
|
250
|
+
event: { type: 'Order Status Equals', info: { source: 'Beluga', status: 'In Transit' } },
|
|
251
|
+
action: buildSetFieldsAction('pharmacy'),
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const postEvent = async (event: string, info?: object) => {
|
|
255
|
+
const res = await fetch(webhookUrl, {
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: { 'Content-Type': 'application/json' },
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
masterId: `tellescope_${formResponse.id}`,
|
|
260
|
+
event,
|
|
261
|
+
orderId: externalOrderId,
|
|
262
|
+
...info ? { info } : {},
|
|
263
|
+
}),
|
|
264
|
+
})
|
|
265
|
+
assert(res.status === 200, `Beluga webhook (${event}) expected 200, got ${res.status}`)
|
|
266
|
+
await wait(undefined, 250) // webhook upsert + trigger + Set Fields
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const getOrder = async () => {
|
|
270
|
+
const orders = await sdk.api.enduser_orders.getSome({
|
|
271
|
+
filter: { source: 'Beluga', externalId: externalOrderId } as any,
|
|
272
|
+
})
|
|
273
|
+
return orders[0]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Order ships first, then tracking updates arrive
|
|
278
|
+
await postEvent('PHARMACY_ORDER_SHIPPED', { carrier: 'USPS', tracking: '9400-CCC' })
|
|
279
|
+
|
|
280
|
+
await postEvent('PACKAGE_IN_TRANSIT', {
|
|
281
|
+
trackerStatus: 'in_transit',
|
|
282
|
+
trackerId: 'trk_123',
|
|
283
|
+
trackingUrl,
|
|
284
|
+
tracking: '9400-CCC',
|
|
285
|
+
carrier: 'USPS',
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
await async_test(
|
|
289
|
+
"Block C: PACKAGE_IN_TRANSIT fires 'In Transit' trigger with trackingUrl preferred over tracking",
|
|
290
|
+
() => sdk.api.endusers.getOne(enduser.id),
|
|
291
|
+
{ onResult: e => !!(
|
|
292
|
+
e.fields?.pharmacy_status === 'In Transit'
|
|
293
|
+
&& e.fields?.pharmacy_tracking === trackingUrl
|
|
294
|
+
&& e.fields?.pharmacy_carrier === 'USPS'
|
|
295
|
+
&& e.fields?.pharmacy_externalId === externalOrderId
|
|
296
|
+
)}
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
await postEvent('PACKAGE_OUT_FOR_DELIVERY', {
|
|
300
|
+
trackerStatus: 'out_for_delivery',
|
|
301
|
+
trackingUrl,
|
|
302
|
+
carrier: 'USPS',
|
|
303
|
+
})
|
|
304
|
+
await async_test(
|
|
305
|
+
"Block C: PACKAGE_OUT_FOR_DELIVERY sets order status 'Out for Delivery'",
|
|
306
|
+
getOrder,
|
|
307
|
+
{ onResult: o => o?.status === 'Out for Delivery' }
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
const deliveredDate = new Date().toISOString()
|
|
311
|
+
await postEvent('PACKAGE_DELIVERED', {
|
|
312
|
+
trackerStatus: 'delivered',
|
|
313
|
+
trackingUrl,
|
|
314
|
+
carrier: 'USPS',
|
|
315
|
+
deliveredDate,
|
|
316
|
+
})
|
|
317
|
+
await async_test(
|
|
318
|
+
"Block C: PACKAGE_DELIVERED sets status 'Delivered' and stores deliveredDate",
|
|
319
|
+
getOrder,
|
|
320
|
+
{ onResult: o => !!(
|
|
321
|
+
o?.status === 'Delivered'
|
|
322
|
+
&& o?.deliveredDate === deliveredDate
|
|
323
|
+
)}
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
await postEvent('PACKAGE_DELIVERY_FAILED', { trackerStatus: 'failure' })
|
|
327
|
+
await async_test(
|
|
328
|
+
"Block C: PACKAGE_DELIVERY_FAILED sets order status 'Delivery Failed'",
|
|
329
|
+
getOrder,
|
|
330
|
+
{ onResult: o => o?.status === 'Delivery Failed' }
|
|
331
|
+
)
|
|
332
|
+
} finally {
|
|
333
|
+
// Clean up the order created by the webhook
|
|
334
|
+
try {
|
|
335
|
+
const orders = await sdk.api.enduser_orders.getSome({
|
|
336
|
+
filter: { source: 'Beluga', externalId: externalOrderId } as any,
|
|
337
|
+
})
|
|
338
|
+
for (const o of orders) {
|
|
339
|
+
await sdk.api.enduser_orders.deleteOne(o.id).catch(console.error)
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
console.error(err)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
await sdk.api.automation_triggers.deleteOne(inTransitTrigger.id).catch(console.error)
|
|
346
|
+
await sdk.api.form_responses.deleteOne(formResponse.id).catch(console.error)
|
|
347
|
+
await sdk.api.forms.deleteOne(form.id).catch(console.error)
|
|
348
|
+
await sdk.api.endusers.deleteOne(enduser.id).catch(console.error)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
231
352
|
export const set_fields_order_templates_tests = async (
|
|
232
353
|
{ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }
|
|
233
354
|
) => {
|
|
234
355
|
log_header("Set Fields: {{order.*}} template resolution")
|
|
235
356
|
await direct_order_creation_block({ sdk })
|
|
236
357
|
await beluga_webhook_block({ sdk })
|
|
358
|
+
await beluga_tracking_status_block({ sdk })
|
|
237
359
|
}
|
|
238
360
|
|
|
239
361
|
if (require.main === module) {
|
package/src/tests/tests.ts
CHANGED
|
@@ -114,6 +114,7 @@ import { organization_settings_duplicates_tests } from "./api_tests/organization
|
|
|
114
114
|
import { calendar_events_bulk_update_tests } from "./api_tests/calendar_events_bulk_update.test";
|
|
115
115
|
import { openloop_webhooks_tests } from "./api_tests/openloop_webhooks.test";
|
|
116
116
|
import { beluga_pharmacy_mappings_tests } from "./api_tests/beluga_pharmacy_mappings.test";
|
|
117
|
+
import { mdi_webhooks_tests } from "./api_tests/mdi_webhooks.test";
|
|
117
118
|
import { account_switcher_tests } from "./api_tests/account_switcher.test";
|
|
118
119
|
import { set_fields_order_templates_tests } from "./api_tests/set_fields_order_templates.test";
|
|
119
120
|
import { date_string_validation_tests } from "./api_tests/date_string_validation.test";
|
|
@@ -14357,6 +14358,7 @@ const ip_address_form_tests = async () => {
|
|
|
14357
14358
|
await form_submitted_trigger_tests({ sdk, sdkNonAdmin })
|
|
14358
14359
|
await date_string_validation_tests({ sdk, sdkNonAdmin })
|
|
14359
14360
|
await openloop_webhooks_tests({ sdk, sdkNonAdmin })
|
|
14361
|
+
await mdi_webhooks_tests({ sdk, sdkNonAdmin })
|
|
14360
14362
|
await integrations_redacted_tests({ sdk, sdkNonAdmin })
|
|
14361
14363
|
await mdb_sort_tests({ sdk, sdkNonAdmin })
|
|
14362
14364
|
await search_tests()
|
package/test_generated.pdf
CHANGED
|
Binary file
|