@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.
Files changed (61) hide show
  1. package/lib/cjs/tests/api_tests/metriport_push_form_response.test.d.ts +6 -0
  2. package/lib/cjs/tests/api_tests/metriport_push_form_response.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/metriport_push_form_response.test.js +298 -0
  4. package/lib/cjs/tests/api_tests/metriport_push_form_response.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.d.ts +6 -0
  6. package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.js +274 -0
  8. package/lib/cjs/tests/api_tests/phone_calls_move_enduser.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts +18 -0
  10. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js +964 -0
  12. package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts +6 -0
  14. package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts.map +1 -0
  15. package/lib/cjs/tests/api_tests/welle_partner_token.test.js +313 -0
  16. package/lib/cjs/tests/api_tests/welle_partner_token.test.js.map +1 -0
  17. package/lib/cjs/tests/stripe_webhook_helpers.d.ts +204 -0
  18. package/lib/cjs/tests/stripe_webhook_helpers.d.ts.map +1 -0
  19. package/lib/cjs/tests/stripe_webhook_helpers.js +286 -0
  20. package/lib/cjs/tests/stripe_webhook_helpers.js.map +1 -0
  21. package/lib/cjs/tests/tests.d.ts.map +1 -1
  22. package/lib/cjs/tests/tests.js +192 -180
  23. package/lib/cjs/tests/tests.js.map +1 -1
  24. package/lib/cjs/tests/unit_tests/translation.test.d.ts +3 -0
  25. package/lib/cjs/tests/unit_tests/translation.test.d.ts.map +1 -0
  26. package/lib/cjs/tests/unit_tests/translation.test.js.map +1 -0
  27. package/lib/esm/tests/api_tests/metriport_push_form_response.test.d.ts +6 -0
  28. package/lib/esm/tests/api_tests/metriport_push_form_response.test.d.ts.map +1 -0
  29. package/lib/esm/tests/api_tests/metriport_push_form_response.test.js +294 -0
  30. package/lib/esm/tests/api_tests/metriport_push_form_response.test.js.map +1 -0
  31. package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.d.ts +6 -0
  32. package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.d.ts.map +1 -0
  33. package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.js +270 -0
  34. package/lib/esm/tests/api_tests/phone_calls_move_enduser.test.js.map +1 -0
  35. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts +18 -0
  36. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -0
  37. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js +960 -0
  38. package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -0
  39. package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts +6 -0
  40. package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts.map +1 -0
  41. package/lib/esm/tests/api_tests/welle_partner_token.test.js +309 -0
  42. package/lib/esm/tests/api_tests/welle_partner_token.test.js.map +1 -0
  43. package/lib/esm/tests/stripe_webhook_helpers.d.ts +204 -0
  44. package/lib/esm/tests/stripe_webhook_helpers.d.ts.map +1 -0
  45. package/lib/esm/tests/stripe_webhook_helpers.js +267 -0
  46. package/lib/esm/tests/stripe_webhook_helpers.js.map +1 -0
  47. package/lib/esm/tests/tests.d.ts.map +1 -1
  48. package/lib/esm/tests/tests.js +192 -180
  49. package/lib/esm/tests/tests.js.map +1 -1
  50. package/lib/esm/tests/unit_tests/translation.test.d.ts +3 -0
  51. package/lib/esm/tests/unit_tests/translation.test.d.ts.map +1 -0
  52. package/lib/esm/tests/unit_tests/translation.test.js +52 -0
  53. package/lib/esm/tests/unit_tests/translation.test.js.map +1 -0
  54. package/lib/tsconfig.tsbuildinfo +1 -1
  55. package/package.json +10 -10
  56. package/src/tests/api_tests/metriport_push_form_response.test.ts +226 -0
  57. package/src/tests/api_tests/stripe_webhook_triggers.test.ts +676 -0
  58. package/src/tests/api_tests/welle_partner_token.test.ts +249 -0
  59. package/src/tests/stripe_webhook_helpers.ts +240 -0
  60. package/src/tests/tests.ts +6 -0
  61. 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.2",
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.2",
36
- "@tellescope/schema": "1.256.2",
37
- "@tellescope/testing": "1.256.2",
38
- "@tellescope/types-client": "1.256.2",
39
- "@tellescope/types-models": "1.256.2",
40
- "@tellescope/types-utilities": "1.256.2",
41
- "@tellescope/utilities": "1.256.2",
42
- "@tellescope/validation": "1.256.2",
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": "a796b01e2a6ccc1f40aeb07084d8b8922e4d078e"
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
+ }