@tellescope/sdk 1.256.2 → 1.256.3
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/metriport_push_form_response.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/metriport_push_form_response.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/metriport_push_form_response.test.js +298 -0
- package/lib/cjs/tests/api_tests/metriport_push_form_response.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.js +274 -0
- package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts +18 -0
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js +964 -0
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js +313 -0
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js.map +1 -0
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts +204 -0
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts.map +1 -0
- package/lib/cjs/tests/stripe_webhook_helpers.js +286 -0
- package/lib/cjs/tests/stripe_webhook_helpers.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +192 -180
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/unit_tests/translation.test.d.ts +3 -0
- package/lib/cjs/tests/unit_tests/translation.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/translation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/metriport_push_form_response.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/metriport_push_form_response.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/metriport_push_form_response.test.js +294 -0
- package/lib/esm/tests/api_tests/metriport_push_form_response.test.js.map +1 -0
- package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.js +270 -0
- package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.js.map +1 -0
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts +18 -0
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js +960 -0
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -0
- package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/welle_partner_token.test.js +309 -0
- package/lib/esm/tests/api_tests/welle_partner_token.test.js.map +1 -0
- package/lib/esm/tests/stripe_webhook_helpers.d.ts +204 -0
- package/lib/esm/tests/stripe_webhook_helpers.d.ts.map +1 -0
- package/lib/esm/tests/stripe_webhook_helpers.js +267 -0
- package/lib/esm/tests/stripe_webhook_helpers.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +192 -180
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/unit_tests/translation.test.d.ts +3 -0
- package/lib/esm/tests/unit_tests/translation.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/translation.test.js +52 -0
- package/lib/esm/tests/unit_tests/translation.test.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/metriport_push_form_response.test.ts +226 -0
- package/src/tests/api_tests/stripe_webhook_triggers.test.ts +676 -0
- package/src/tests/api_tests/welle_partner_token.test.ts +249 -0
- package/src/tests/stripe_webhook_helpers.ts +240 -0
- package/src/tests/tests.ts +6 -0
- package/test_generated.pdf +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/sdk",
|
|
3
|
-
"version": "1.256.
|
|
3
|
+
"version": "1.256.3",
|
|
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.3",
|
|
36
|
+
"@tellescope/schema": "1.256.3",
|
|
37
|
+
"@tellescope/testing": "1.256.3",
|
|
38
|
+
"@tellescope/types-client": "1.256.3",
|
|
39
|
+
"@tellescope/types-models": "1.256.3",
|
|
40
|
+
"@tellescope/types-utilities": "1.256.3",
|
|
41
|
+
"@tellescope/utilities": "1.256.3",
|
|
42
|
+
"@tellescope/validation": "1.256.3",
|
|
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": "0f451abd251f9b79af46bffb994b5e815477bd49"
|
|
59
59
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
assert,
|
|
6
|
+
async_test,
|
|
7
|
+
log_header,
|
|
8
|
+
} from "@tellescope/testing"
|
|
9
|
+
import { setup_tests } from "../setup"
|
|
10
|
+
import { METRIPORT_TITLE } from "@tellescope/constants"
|
|
11
|
+
|
|
12
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
13
|
+
|
|
14
|
+
// metriportPushFormResponse journey action (CU-86e2cucba).
|
|
15
|
+
//
|
|
16
|
+
// Fast / no-upload: this exercises validation and the guard branches that reject before any
|
|
17
|
+
// Metriport HTTP call. A real upload needs a Metriport sandbox API key plus a patient already
|
|
18
|
+
// synced to a facility, so the happy path is verified manually (see the task plan).
|
|
19
|
+
export const metriport_push_form_response_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
|
|
20
|
+
log_header("Metriport Push Form Response Tests")
|
|
21
|
+
|
|
22
|
+
let enduserId: string | undefined
|
|
23
|
+
let otherEnduserId: string | undefined
|
|
24
|
+
let formId: string | undefined
|
|
25
|
+
let journeyId: string | undefined
|
|
26
|
+
let createdIntegrationId: string | undefined
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const journey = await sdk.api.journeys.createOne({ title: 'Metriport Push Form Response Journey' })
|
|
30
|
+
journeyId = journey.id
|
|
31
|
+
|
|
32
|
+
const form = await sdk.api.forms.createOne({ title: 'Metriport Push Form Response Form' })
|
|
33
|
+
formId = form.id
|
|
34
|
+
const field = await sdk.api.form_fields.createOne({
|
|
35
|
+
formId: form.id, type: 'string', title: 'Chief Complaint', previousFields: [{ type: 'root', info: {} }],
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const enduser = await sdk.api.endusers.createOne({
|
|
39
|
+
fname: 'metriport-push',
|
|
40
|
+
lname: 'test',
|
|
41
|
+
email: `metriport_push_${Date.now()}@test.tellescope.com`,
|
|
42
|
+
})
|
|
43
|
+
enduserId = enduser.id
|
|
44
|
+
|
|
45
|
+
/*************** validation ***************/
|
|
46
|
+
const step = await sdk.api.automation_steps.createOne({
|
|
47
|
+
journeyId: journey.id,
|
|
48
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
49
|
+
action: { type: 'metriportPushFormResponse', info: { formId: form.id, integrationTitle: '' } },
|
|
50
|
+
})
|
|
51
|
+
assert(
|
|
52
|
+
step.action.type === 'metriportPushFormResponse' && step.action.info.formId === form.id,
|
|
53
|
+
'metriportPushFormResponse step round-trip failed',
|
|
54
|
+
'metriportPushFormResponse step round-trips',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// formId is optional — a step with no info at all is valid (uses the journey's form response)
|
|
58
|
+
const contextOnlyStep = await sdk.api.automation_steps.createOne({
|
|
59
|
+
journeyId: journey.id,
|
|
60
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
61
|
+
action: { type: 'metriportPushFormResponse', info: {} },
|
|
62
|
+
})
|
|
63
|
+
assert(
|
|
64
|
+
contextOnlyStep.action.type === 'metriportPushFormResponse',
|
|
65
|
+
'metriportPushFormResponse step without info was rejected',
|
|
66
|
+
'metriportPushFormResponse info is optional',
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
await async_test('metriportPushFormResponse with a malformed formId is rejected',
|
|
70
|
+
() => sdk.api.automation_steps.createOne({
|
|
71
|
+
journeyId: journey.id,
|
|
72
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
73
|
+
action: { type: 'metriportPushFormResponse', info: { formId: 'not-a-mongo-id' } },
|
|
74
|
+
}),
|
|
75
|
+
{ shouldError: true, onError: () => true },
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
/*************** runtime ***************/
|
|
79
|
+
const { accessCode, response } = await sdk.api.form_responses.prepare_form_response({
|
|
80
|
+
enduserId: enduser.id, formId: form.id,
|
|
81
|
+
})
|
|
82
|
+
await sdk.api.form_responses.submit_form_response({
|
|
83
|
+
accessCode,
|
|
84
|
+
responses: [{ fieldId: field.id, fieldTitle: 'Chief Complaint', answer: { type: 'string', value: 'Headache' } }],
|
|
85
|
+
})
|
|
86
|
+
const formResponseId = response.id
|
|
87
|
+
|
|
88
|
+
const processStep = async () => {
|
|
89
|
+
const automatedAction = await sdk.api.automated_actions.createOne({
|
|
90
|
+
enduserId: enduser.id,
|
|
91
|
+
journeyId: journey.id,
|
|
92
|
+
automationStepId: step.id,
|
|
93
|
+
event: { type: 'onJourneyStart', info: {} },
|
|
94
|
+
action: { type: 'metriportPushFormResponse', info: { formId: form.id } },
|
|
95
|
+
journeyContext: { formResponseId },
|
|
96
|
+
status: 'active',
|
|
97
|
+
processAfter: Date.now(),
|
|
98
|
+
})
|
|
99
|
+
return sdk.api.automated_actions.process({ automatedActionId: automatedAction.id })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The org may already have a Metriport integration; only the no-integration assertion depends
|
|
103
|
+
// on it being absent, so check first.
|
|
104
|
+
let metriportIntegrationExists = false
|
|
105
|
+
try {
|
|
106
|
+
const existing = await sdk.api.integrations.load_redacted({})
|
|
107
|
+
metriportIntegrationExists = !!existing.integrations.find((i: any) => i.title === METRIPORT_TITLE)
|
|
108
|
+
} catch { /* load not permitted — treat as absent and let the assertions below tell us */ }
|
|
109
|
+
|
|
110
|
+
if (!metriportIntegrationExists) {
|
|
111
|
+
const noIntegrationResult = await processStep()
|
|
112
|
+
assert(
|
|
113
|
+
!noIntegrationResult.success
|
|
114
|
+
&& !noIntegrationResult.unimplemented
|
|
115
|
+
&& noIntegrationResult.error === 'Metriport integration not found',
|
|
116
|
+
`expected 'Metriport integration not found', got: ${JSON.stringify(noIntegrationResult)}`,
|
|
117
|
+
'metriportPushFormResponse is wired into automated_actions.process (no integration)',
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const created = await sdk.api.integrations.createOne({
|
|
121
|
+
title: METRIPORT_TITLE,
|
|
122
|
+
authentication: {
|
|
123
|
+
type: 'apiKey',
|
|
124
|
+
info: { access_token: 'test-metriport-api-key', refresh_token: 'unused', scope: '', token_type: 'Bearer', expiry_date: new Date().getTime() },
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
createdIntegrationId = created.id
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// With an integration but a patient that was never synced to a facility, the action fails
|
|
131
|
+
// before any HTTP call to Metriport
|
|
132
|
+
const unsyncedResult = await processStep()
|
|
133
|
+
assert(
|
|
134
|
+
!unsyncedResult.success
|
|
135
|
+
&& !unsyncedResult.unimplemented
|
|
136
|
+
&& /not synced to Metriport/i.test(unsyncedResult.error || ''),
|
|
137
|
+
`expected a 'Patient is not synced to Metriport' error, got: ${JSON.stringify(unsyncedResult)}`,
|
|
138
|
+
'metriportPushFormResponse rejects a patient not synced to Metriport',
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
/*************** cross-patient ownership (CU-86e2cucba security fix) ***************/
|
|
142
|
+
// A journeyContext.formResponseId belonging to patient A must never be exported under a
|
|
143
|
+
// different patient B — the switchToRelatedContact path can move enduserId off the form
|
|
144
|
+
// response's owner while journeyContext still points at A's response.
|
|
145
|
+
const otherEnduser = await sdk.api.endusers.createOne({
|
|
146
|
+
fname: 'metriport-push-other',
|
|
147
|
+
lname: 'test',
|
|
148
|
+
email: `metriport_push_other_${Date.now()}@test.tellescope.com`,
|
|
149
|
+
})
|
|
150
|
+
otherEnduserId = otherEnduser.id
|
|
151
|
+
|
|
152
|
+
const crossPatientAction = await sdk.api.automated_actions.createOne({
|
|
153
|
+
enduserId: otherEnduser.id, // patient B
|
|
154
|
+
journeyId: journey.id,
|
|
155
|
+
automationStepId: step.id,
|
|
156
|
+
event: { type: 'onJourneyStart', info: {} },
|
|
157
|
+
action: { type: 'metriportPushFormResponse', info: { formId: form.id } },
|
|
158
|
+
journeyContext: { formResponseId }, // patient A's submitted response
|
|
159
|
+
status: 'active',
|
|
160
|
+
processAfter: Date.now(),
|
|
161
|
+
})
|
|
162
|
+
const crossPatientResult = await sdk.api.automated_actions.process({ automatedActionId: crossPatientAction.id })
|
|
163
|
+
assert(
|
|
164
|
+
!crossPatientResult.success
|
|
165
|
+
&& !crossPatientResult.unimplemented
|
|
166
|
+
&& crossPatientResult.error === 'Form response does not belong to this patient',
|
|
167
|
+
`expected a cross-patient ownership rejection, got: ${JSON.stringify(crossPatientResult)}`,
|
|
168
|
+
'metriportPushFormResponse rejects a form response owned by a different patient',
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// the rejection must occur before any Metriport reference is stamped on A's response
|
|
172
|
+
const untouched = await sdk.api.form_responses.getOne(formResponseId)
|
|
173
|
+
assert(
|
|
174
|
+
!(untouched as any).references?.find((r: any) => r.type === METRIPORT_TITLE),
|
|
175
|
+
`patient A's form response was stamped despite a cross-patient rejection: ${JSON.stringify((untouched as any).references)}`,
|
|
176
|
+
'metriportPushFormResponse does not stamp a form response on cross-patient rejection',
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
/*************** idempotency ***************/
|
|
180
|
+
// form_responses.references is a readonly field, so it can only be stamped server-side (which
|
|
181
|
+
// is what a successful upload does). Attempt the stamp; if the API drops it, skip the check
|
|
182
|
+
// rather than assert on a state we couldn't set up.
|
|
183
|
+
await sdk.api.form_responses.updateOne(formResponseId, {
|
|
184
|
+
references: [{ type: METRIPORT_TITLE, id: 'test-document-reference-id' }],
|
|
185
|
+
} as any).catch(() => {})
|
|
186
|
+
|
|
187
|
+
const stamped = await sdk.api.form_responses.getOne(formResponseId)
|
|
188
|
+
if ((stamped as any).references?.find((r: any) => r.type === METRIPORT_TITLE)) {
|
|
189
|
+
const idempotentResult = await processStep()
|
|
190
|
+
assert(
|
|
191
|
+
idempotentResult.success === true,
|
|
192
|
+
`expected a no-op success for an already-shared form response, got: ${JSON.stringify(idempotentResult)}`,
|
|
193
|
+
'metriportPushFormResponse no-ops for an already-shared form response',
|
|
194
|
+
)
|
|
195
|
+
} else {
|
|
196
|
+
console.log("⏭️ Skipping idempotency check (form_responses.references is readonly over the API)")
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
if (createdIntegrationId) await sdk.api.integrations.deleteOne(createdIntegrationId).catch(console.error)
|
|
200
|
+
if (journeyId) await sdk.api.journeys.deleteOne(journeyId).catch(console.error)
|
|
201
|
+
if (formId) await sdk.api.forms.deleteOne(formId).catch(console.error)
|
|
202
|
+
if (enduserId) await sdk.api.endusers.deleteOne(enduserId).catch(console.error)
|
|
203
|
+
if (otherEnduserId) await sdk.api.endusers.deleteOne(otherEnduserId).catch(console.error)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
console.log(`Using API URL: ${host}`)
|
|
209
|
+
const sdk = new Session({ host })
|
|
210
|
+
const sdkNonAdmin = new Session({ host })
|
|
211
|
+
|
|
212
|
+
const runTests = async () => {
|
|
213
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
214
|
+
await metriport_push_form_response_tests({ sdk, sdkNonAdmin })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
runTests()
|
|
218
|
+
.then(() => {
|
|
219
|
+
console.log("✅ Metriport push form response test suite completed successfully")
|
|
220
|
+
process.exit(0)
|
|
221
|
+
})
|
|
222
|
+
.catch((error) => {
|
|
223
|
+
console.error("❌ Metriport push form response test suite failed:", error)
|
|
224
|
+
process.exit(1)
|
|
225
|
+
})
|
|
226
|
+
}
|