@tellescope/sdk 1.248.0 → 1.249.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/.env +3 -0
- package/lib/cjs/sdk.d.ts +1 -0
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +1 -0
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/api_tests/chats_analytics.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/chats_analytics.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/chats_analytics.test.js +256 -0
- package/lib/cjs/tests/api_tests/chats_analytics.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/cross_org_api_key.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/cross_org_api_key.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/cross_org_api_key.test.js +748 -0
- package/lib/cjs/tests/api_tests/cross_org_api_key.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js +426 -2
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/eom_billing_codes.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/eom_billing_codes.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/eom_billing_codes.test.js +162 -0
- package/lib/cjs/tests/api_tests/eom_billing_codes.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/eom_procedure_codes.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/eom_procedure_codes.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/eom_procedure_codes.test.js +339 -0
- package/lib/cjs/tests/api_tests/eom_procedure_codes.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/managed_content_file_access.test.d.ts +13 -0
- package/lib/cjs/tests/api_tests/managed_content_file_access.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/managed_content_file_access.test.js +385 -0
- package/lib/cjs/tests/api_tests/managed_content_file_access.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.js +25 -2
- package/lib/cjs/tests/api_tests/organization_settings_duplicates.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +148 -132
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/sdk.d.ts +3 -2
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +1 -0
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/session.d.ts +1 -0
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/chats_analytics.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/chats_analytics.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/chats_analytics.test.js +252 -0
- package/lib/esm/tests/api_tests/chats_analytics.test.js.map +1 -0
- package/lib/esm/tests/api_tests/cross_org_api_key.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/cross_org_api_key.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/cross_org_api_key.test.js +744 -0
- package/lib/esm/tests/api_tests/cross_org_api_key.test.js.map +1 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js +426 -2
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js.map +1 -1
- package/lib/esm/tests/api_tests/eom_billing_codes.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/eom_billing_codes.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/eom_billing_codes.test.js +158 -0
- package/lib/esm/tests/api_tests/eom_billing_codes.test.js.map +1 -0
- package/lib/esm/tests/api_tests/eom_procedure_codes.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/eom_procedure_codes.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/eom_procedure_codes.test.js +335 -0
- package/lib/esm/tests/api_tests/eom_procedure_codes.test.js.map +1 -0
- package/lib/esm/tests/api_tests/managed_content_file_access.test.d.ts +13 -0
- package/lib/esm/tests/api_tests/managed_content_file_access.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/managed_content_file_access.test.js +358 -0
- package/lib/esm/tests/api_tests/managed_content_file_access.test.js.map +1 -0
- package/lib/esm/tests/api_tests/organization_settings_duplicates.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/organization_settings_duplicates.test.js +25 -2
- package/lib/esm/tests/api_tests/organization_settings_duplicates.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +148 -132
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/sdk.ts +4 -0
- package/src/tests/api_tests/chats_analytics.test.ts +182 -0
- package/src/tests/api_tests/cross_org_api_key.test.ts +665 -0
- package/src/tests/api_tests/enduser_session_invalidation.test.ts +223 -0
- package/src/tests/api_tests/eom_procedure_codes.test.ts +296 -0
- package/src/tests/api_tests/managed_content_file_access.test.ts +214 -0
- package/src/tests/api_tests/organization_settings_duplicates.test.ts +14 -0
- package/src/tests/tests.ts +10 -2
- package/test_generated.pdf +0 -0
|
@@ -113,6 +113,229 @@ export const enduser_session_invalidation_tests = async ({ sdk, sdkNonAdmin } :
|
|
|
113
113
|
console.error('Cleanup error:', error)
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
// --- OTP enablement invalidation tests ---
|
|
118
|
+
|
|
119
|
+
log_header("OTP Enablement Session Invalidation Tests")
|
|
120
|
+
|
|
121
|
+
// Helper to reset OTP settings
|
|
122
|
+
const resetOTPSettings = async (s: Session) => {
|
|
123
|
+
await s.api.organizations.updateOne(s.userInfo.businessId, {
|
|
124
|
+
portalSettings: { authentication: { requireOTP: false, requireOTPAfterPassword: false } },
|
|
125
|
+
}, { replaceObjectFields: true })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Ensure OTP is off and db is clean before starting
|
|
129
|
+
await sdk.reset_db()
|
|
130
|
+
await wait(undefined, 500)
|
|
131
|
+
await resetOTPSettings(sdk)
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
|
|
135
|
+
// Test 6: Enabling requireOTP invalidates multiple enduser sessions at once
|
|
136
|
+
await async_test(
|
|
137
|
+
'enabling requireOTP invalidates multiple enduser sessions',
|
|
138
|
+
async () => {
|
|
139
|
+
const endusers = await Promise.all([
|
|
140
|
+
sdk.api.endusers.createOne({ email: `otp-bulk-1-${Date.now()}@tellescope.com` }),
|
|
141
|
+
sdk.api.endusers.createOne({ email: `otp-bulk-2-${Date.now()}@tellescope.com` }),
|
|
142
|
+
sdk.api.endusers.createOne({ email: `otp-bulk-3-${Date.now()}@tellescope.com` }),
|
|
143
|
+
])
|
|
144
|
+
try {
|
|
145
|
+
const tokens = await Promise.all(
|
|
146
|
+
endusers.map(e => sdk.api.endusers.generate_auth_token({ id: e.id, overrideOTP: true }))
|
|
147
|
+
)
|
|
148
|
+
const sessions = tokens.map(t => new EnduserSession({ host, authToken: t.authToken, businessId: sdk.userInfo.businessId }))
|
|
149
|
+
|
|
150
|
+
// Verify all tokens work
|
|
151
|
+
for (const s of sessions) {
|
|
152
|
+
await s.test_authenticated()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await wait(undefined, 2000)
|
|
156
|
+
|
|
157
|
+
// Enable OTP
|
|
158
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
159
|
+
portalSettings: { authentication: { requireOTP: true } },
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Wait for side effect to process
|
|
163
|
+
await wait(undefined, 1000)
|
|
164
|
+
|
|
165
|
+
// All old tokens should be rejected
|
|
166
|
+
for (const s of sessions) {
|
|
167
|
+
try {
|
|
168
|
+
await s.test_authenticated()
|
|
169
|
+
return 'should have thrown'
|
|
170
|
+
} catch (e) { /* expected */ }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// New tokens should work
|
|
174
|
+
await wait(undefined, 2000)
|
|
175
|
+
for (const e of endusers) {
|
|
176
|
+
const { authToken: newToken } = await sdk.api.endusers.generate_auth_token({ id: e.id, overrideOTP: true })
|
|
177
|
+
const newSession = new EnduserSession({ host, authToken: newToken, businessId: sdk.userInfo.businessId })
|
|
178
|
+
await newSession.test_authenticated()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return 'passed'
|
|
182
|
+
} finally {
|
|
183
|
+
await resetOTPSettings(sdk)
|
|
184
|
+
for (const e of endusers) {
|
|
185
|
+
await sdk.api.endusers.deleteOne(e.id).catch(console.error)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{ expectedResult: 'passed' }
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// Test 7: Enabling requireOTPAfterPassword invalidates multiple enduser sessions
|
|
193
|
+
await async_test(
|
|
194
|
+
'enabling requireOTPAfterPassword invalidates multiple enduser sessions',
|
|
195
|
+
async () => {
|
|
196
|
+
const endusers = await Promise.all([
|
|
197
|
+
sdk.api.endusers.createOne({ email: `otp-mfa-1-${Date.now()}@tellescope.com` }),
|
|
198
|
+
sdk.api.endusers.createOne({ email: `otp-mfa-2-${Date.now()}@tellescope.com` }),
|
|
199
|
+
])
|
|
200
|
+
try {
|
|
201
|
+
const tokens = await Promise.all(
|
|
202
|
+
endusers.map(e => sdk.api.endusers.generate_auth_token({ id: e.id, overrideOTP: true }))
|
|
203
|
+
)
|
|
204
|
+
const sessions = tokens.map(t => new EnduserSession({ host, authToken: t.authToken, businessId: sdk.userInfo.businessId }))
|
|
205
|
+
|
|
206
|
+
for (const s of sessions) {
|
|
207
|
+
await s.test_authenticated()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await wait(undefined, 2000)
|
|
211
|
+
|
|
212
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
213
|
+
portalSettings: { authentication: { requireOTPAfterPassword: true } },
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
await wait(undefined, 1000)
|
|
217
|
+
|
|
218
|
+
for (const s of sessions) {
|
|
219
|
+
try {
|
|
220
|
+
await s.test_authenticated()
|
|
221
|
+
return 'should have thrown'
|
|
222
|
+
} catch (e) { /* expected */ }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// New tokens work
|
|
226
|
+
await wait(undefined, 2000)
|
|
227
|
+
for (const e of endusers) {
|
|
228
|
+
const { authToken: newToken } = await sdk.api.endusers.generate_auth_token({ id: e.id, overrideOTP: true })
|
|
229
|
+
const newSession = new EnduserSession({ host, authToken: newToken, businessId: sdk.userInfo.businessId })
|
|
230
|
+
await newSession.test_authenticated()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return 'passed'
|
|
234
|
+
} finally {
|
|
235
|
+
await resetOTPSettings(sdk)
|
|
236
|
+
for (const e of endusers) {
|
|
237
|
+
await sdk.api.endusers.deleteOne(e.id).catch(console.error)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{ expectedResult: 'passed' }
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
// Reset rate limiting state before continuing
|
|
245
|
+
await sdk.reset_db()
|
|
246
|
+
await wait(undefined, 500)
|
|
247
|
+
|
|
248
|
+
// Test 8: Disabling OTP does NOT invalidate sessions
|
|
249
|
+
await async_test(
|
|
250
|
+
'disabling OTP does not invalidate sessions',
|
|
251
|
+
async () => {
|
|
252
|
+
// Enable OTP first
|
|
253
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
254
|
+
portalSettings: { authentication: { requireOTP: true } },
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await wait(undefined, 2000)
|
|
258
|
+
|
|
259
|
+
// Create enduser and token AFTER OTP is enabled (so token is valid)
|
|
260
|
+
const enduser = await sdk.api.endusers.createOne({ email: `otp-disable-${Date.now()}@tellescope.com` })
|
|
261
|
+
try {
|
|
262
|
+
const { authToken } = await sdk.api.endusers.generate_auth_token({ id: enduser.id, overrideOTP: true })
|
|
263
|
+
const enduserSession = new EnduserSession({ host, authToken, businessId: sdk.userInfo.businessId })
|
|
264
|
+
await enduserSession.test_authenticated()
|
|
265
|
+
|
|
266
|
+
await wait(undefined, 2000)
|
|
267
|
+
|
|
268
|
+
// Disable OTP
|
|
269
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
270
|
+
portalSettings: { authentication: { requireOTP: false } },
|
|
271
|
+
}, { replaceObjectFields: true })
|
|
272
|
+
|
|
273
|
+
await wait(undefined, 1000)
|
|
274
|
+
|
|
275
|
+
// Old token should still work (disabling OTP should not invalidate)
|
|
276
|
+
return await enduserSession.test_authenticated()
|
|
277
|
+
} finally {
|
|
278
|
+
await resetOTPSettings(sdk)
|
|
279
|
+
await sdk.api.endusers.deleteOne(enduser.id).catch(console.error)
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{ expectedResult: 'Authenticated!' }
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// Test 9: OTP invalidation is scoped to the updated organization only (multi-tenant)
|
|
286
|
+
await async_test(
|
|
287
|
+
'OTP invalidation is scoped to the updated organization only',
|
|
288
|
+
async () => {
|
|
289
|
+
const sdkOther = new Session({ host, apiKey: "ba745e25162bb95a795c5fa1af70df188d93c4d3aac9c48b34a5c8c9dd7b80f7" })
|
|
290
|
+
|
|
291
|
+
const otherEnduser = await sdkOther.api.endusers.createOne({ email: `otp-other-tenant-${Date.now()}@tellescope.com` })
|
|
292
|
+
const mainEnduser = await sdk.api.endusers.createOne({ email: `otp-main-tenant-${Date.now()}@tellescope.com` })
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const { authToken: otherToken } = await sdkOther.api.endusers.generate_auth_token({ id: otherEnduser.id, overrideOTP: true })
|
|
296
|
+
const otherEnduserSession = new EnduserSession({ host, authToken: otherToken, businessId: sdkOther.userInfo.businessId })
|
|
297
|
+
|
|
298
|
+
const { authToken: mainToken } = await sdk.api.endusers.generate_auth_token({ id: mainEnduser.id, overrideOTP: true })
|
|
299
|
+
const mainEnduserSession = new EnduserSession({ host, authToken: mainToken, businessId: sdk.userInfo.businessId })
|
|
300
|
+
|
|
301
|
+
// Both tokens work
|
|
302
|
+
await otherEnduserSession.test_authenticated()
|
|
303
|
+
await mainEnduserSession.test_authenticated()
|
|
304
|
+
|
|
305
|
+
await wait(undefined, 2000)
|
|
306
|
+
|
|
307
|
+
// Enable OTP on main tenant only
|
|
308
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
309
|
+
portalSettings: { authentication: { requireOTP: true } },
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
await wait(undefined, 1000)
|
|
313
|
+
|
|
314
|
+
// Main tenant enduser's old token should be rejected
|
|
315
|
+
try {
|
|
316
|
+
await mainEnduserSession.test_authenticated()
|
|
317
|
+
return 'should have thrown'
|
|
318
|
+
} catch (e) { /* expected */ }
|
|
319
|
+
|
|
320
|
+
// Other tenant enduser's token should still work
|
|
321
|
+
await otherEnduserSession.test_authenticated()
|
|
322
|
+
|
|
323
|
+
return 'passed'
|
|
324
|
+
} finally {
|
|
325
|
+
await resetOTPSettings(sdk)
|
|
326
|
+
await sdk.api.endusers.deleteOne(mainEnduser.id).catch(console.error)
|
|
327
|
+
await sdkOther.api.endusers.deleteOne(otherEnduser.id).catch(console.error)
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{ expectedResult: 'passed' }
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
console.log("✅ All OTP Enablement Session Invalidation tests passed!")
|
|
334
|
+
|
|
335
|
+
} finally {
|
|
336
|
+
// Always reset OTP settings to prevent impacting other tests
|
|
337
|
+
await resetOTPSettings(sdk).catch(console.error)
|
|
338
|
+
}
|
|
116
339
|
}
|
|
117
340
|
|
|
118
341
|
// Allow running this test file independently
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import { async_test, log_header } from "@tellescope/testing"
|
|
5
|
+
import { setup_tests } from "../setup"
|
|
6
|
+
|
|
7
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
8
|
+
|
|
9
|
+
const sampleProcedureCodes = [
|
|
10
|
+
{
|
|
11
|
+
code: 'G0019',
|
|
12
|
+
units: 1,
|
|
13
|
+
feeCents: 12000,
|
|
14
|
+
modifiers: ['25'],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: 'G0022',
|
|
18
|
+
units: 0,
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const sampleDiagnosisCodes = [
|
|
23
|
+
{ code: 'Z71.3', description: 'Dietary counseling and surveillance' },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export const eom_procedure_codes_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
27
|
+
log_header("EOM FormResponse Procedure Codes Tests")
|
|
28
|
+
|
|
29
|
+
const enduser = await sdk.api.endusers.createOne({ fname: 'EOM', lname: 'Test' })
|
|
30
|
+
const createdFormIds: string[] = []
|
|
31
|
+
const createdFormResponseIds: string[] = []
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Bootstrap a base FormResponse via prepare_form_response (bypasses the
|
|
35
|
+
// form_responses.createOne path that requires non-empty responses).
|
|
36
|
+
const baseForm = await sdk.api.forms.createOne({ title: 'EOM Procedure Codes Base Form' })
|
|
37
|
+
createdFormIds.push(baseForm.id)
|
|
38
|
+
const { response: formResponse } = await sdk.api.form_responses.prepare_form_response({
|
|
39
|
+
formId: baseForm.id,
|
|
40
|
+
enduserId: enduser.id,
|
|
41
|
+
})
|
|
42
|
+
createdFormResponseIds.push(formResponse.id)
|
|
43
|
+
|
|
44
|
+
// ---------- FormResponse-level field tests (existing behavior) ----------
|
|
45
|
+
await async_test(
|
|
46
|
+
'updateOne accepts procedureCodes and diagnosisCodes on FormResponse',
|
|
47
|
+
() => sdk.api.form_responses.updateOne(formResponse.id, {
|
|
48
|
+
procedureCodes: sampleProcedureCodes,
|
|
49
|
+
diagnosisCodes: sampleDiagnosisCodes,
|
|
50
|
+
}),
|
|
51
|
+
{ onResult: r => !!r && Array.isArray(r.procedureCodes) && r.procedureCodes.length === 2 && Array.isArray(r.diagnosisCodes) && r.diagnosisCodes.length === 1 }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
await async_test(
|
|
55
|
+
'getOne returns persisted procedureCodes/diagnosisCodes',
|
|
56
|
+
() => sdk.api.form_responses.getOne(formResponse.id),
|
|
57
|
+
{ onResult: r => (
|
|
58
|
+
r.procedureCodes?.[0]?.code === 'G0019'
|
|
59
|
+
&& r.procedureCodes?.[0]?.units === 1
|
|
60
|
+
&& r.procedureCodes?.[0]?.feeCents === 12000
|
|
61
|
+
&& r.procedureCodes?.[1]?.code === 'G0022'
|
|
62
|
+
&& r.procedureCodes?.[1]?.units === 0
|
|
63
|
+
&& r.diagnosisCodes?.[0]?.code === 'Z71.3'
|
|
64
|
+
) }
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
await async_test(
|
|
68
|
+
'updateOne rejects FormResponse procedureCode with non-string code',
|
|
69
|
+
() => sdk.api.form_responses.updateOne(formResponse.id, {
|
|
70
|
+
procedureCodes: [{ code: 12345 as any, units: 1 }],
|
|
71
|
+
}),
|
|
72
|
+
{ shouldError: true, onError: () => true }
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
await async_test(
|
|
76
|
+
'updateOne rejects FormResponse procedureCode with negative units',
|
|
77
|
+
() => sdk.api.form_responses.updateOne(formResponse.id, {
|
|
78
|
+
procedureCodes: [{ code: 'G0019', units: -1 }],
|
|
79
|
+
}),
|
|
80
|
+
{ shouldError: true, onError: () => true }
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
await async_test(
|
|
84
|
+
'updateOne rejects FormResponse diagnosisCode with non-string code',
|
|
85
|
+
() => sdk.api.form_responses.updateOne(formResponse.id, {
|
|
86
|
+
diagnosisCodes: [{ code: 12345 as any }],
|
|
87
|
+
}),
|
|
88
|
+
{ shouldError: true, onError: () => true }
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await async_test(
|
|
92
|
+
'updateOne can clear procedureCodes/diagnosisCodes with empty arrays via replaceObjectFields',
|
|
93
|
+
() => sdk.api.form_responses.updateOne(formResponse.id, {
|
|
94
|
+
procedureCodes: [],
|
|
95
|
+
diagnosisCodes: [],
|
|
96
|
+
}, { replaceObjectFields: true }),
|
|
97
|
+
{ onResult: r => (
|
|
98
|
+
Array.isArray(r.procedureCodes) && r.procedureCodes.length === 0
|
|
99
|
+
&& Array.isArray(r.diagnosisCodes) && r.diagnosisCodes.length === 0
|
|
100
|
+
) }
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// ---------- Form-level field validators ----------
|
|
104
|
+
await async_test(
|
|
105
|
+
'forms.createOne accepts valid procedureCodes/diagnosisCodes',
|
|
106
|
+
() => sdk.api.forms.createOne({
|
|
107
|
+
title: 'EOM Procedure Codes Form (valid codes)',
|
|
108
|
+
procedureCodes: sampleProcedureCodes,
|
|
109
|
+
diagnosisCodes: sampleDiagnosisCodes,
|
|
110
|
+
}),
|
|
111
|
+
{ onResult: f => {
|
|
112
|
+
createdFormIds.push(f.id)
|
|
113
|
+
return f.procedureCodes?.length === 2
|
|
114
|
+
&& f.procedureCodes?.[0]?.code === 'G0019'
|
|
115
|
+
&& f.diagnosisCodes?.[0]?.code === 'Z71.3'
|
|
116
|
+
} }
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
await async_test(
|
|
120
|
+
'forms.createOne rejects procedureCode with non-string code',
|
|
121
|
+
() => sdk.api.forms.createOne({
|
|
122
|
+
title: 'EOM Bad Form',
|
|
123
|
+
procedureCodes: [{ code: 12345 as any, units: 1 }],
|
|
124
|
+
}),
|
|
125
|
+
{ shouldError: true, onError: () => true }
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
await async_test(
|
|
129
|
+
'forms.createOne rejects procedureCode with negative units',
|
|
130
|
+
() => sdk.api.forms.createOne({
|
|
131
|
+
title: 'EOM Bad Form',
|
|
132
|
+
procedureCodes: [{ code: 'G0019', units: -1 }],
|
|
133
|
+
}),
|
|
134
|
+
{ shouldError: true, onError: () => true }
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
await async_test(
|
|
138
|
+
'forms.createOne rejects diagnosisCode with non-string code',
|
|
139
|
+
() => sdk.api.forms.createOne({
|
|
140
|
+
title: 'EOM Bad Form',
|
|
141
|
+
diagnosisCodes: [{ code: 12345 as any }],
|
|
142
|
+
}),
|
|
143
|
+
{ shouldError: true, onError: () => true }
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
// ---------- prepare_form_response copy-on-prepare from Form ----------
|
|
147
|
+
const formWithCodes = await sdk.api.forms.createOne({
|
|
148
|
+
title: 'EOM Procedure Codes Form (with codes)',
|
|
149
|
+
procedureCodes: sampleProcedureCodes,
|
|
150
|
+
diagnosisCodes: sampleDiagnosisCodes,
|
|
151
|
+
})
|
|
152
|
+
createdFormIds.push(formWithCodes.id)
|
|
153
|
+
|
|
154
|
+
let preparedFromFormResponseId = ''
|
|
155
|
+
await async_test(
|
|
156
|
+
'prepare_form_response copies procedureCodes/diagnosisCodes from Form when no args',
|
|
157
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
158
|
+
formId: formWithCodes.id,
|
|
159
|
+
enduserId: enduser.id,
|
|
160
|
+
}),
|
|
161
|
+
{ onResult: r => {
|
|
162
|
+
preparedFromFormResponseId = r.response.id
|
|
163
|
+
createdFormResponseIds.push(r.response.id)
|
|
164
|
+
return r.response.procedureCodes?.length === 2
|
|
165
|
+
&& r.response.procedureCodes?.[0]?.code === 'G0019'
|
|
166
|
+
&& r.response.diagnosisCodes?.[0]?.code === 'Z71.3'
|
|
167
|
+
} }
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
await async_test(
|
|
171
|
+
'persisted FormResponse retains Form codes after prepare',
|
|
172
|
+
() => sdk.api.form_responses.getOne(preparedFromFormResponseId),
|
|
173
|
+
{ onResult: r => (
|
|
174
|
+
r.procedureCodes?.length === 2
|
|
175
|
+
&& r.procedureCodes?.[0]?.code === 'G0019'
|
|
176
|
+
&& r.diagnosisCodes?.[0]?.code === 'Z71.3'
|
|
177
|
+
) }
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// ---------- prepare args override Form defaults ----------
|
|
181
|
+
const overrideProcedureCodes = [{ code: '99213', units: 1 }]
|
|
182
|
+
const overrideDiagnosisCodes = [{ code: 'E11.9', description: 'Type 2 diabetes' }]
|
|
183
|
+
|
|
184
|
+
let overridePreparedResponseId = ''
|
|
185
|
+
await async_test(
|
|
186
|
+
'prepare_form_response args override Form defaults',
|
|
187
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
188
|
+
formId: formWithCodes.id,
|
|
189
|
+
enduserId: enduser.id,
|
|
190
|
+
procedureCodes: overrideProcedureCodes,
|
|
191
|
+
diagnosisCodes: overrideDiagnosisCodes,
|
|
192
|
+
}),
|
|
193
|
+
{ onResult: r => {
|
|
194
|
+
overridePreparedResponseId = r.response.id
|
|
195
|
+
createdFormResponseIds.push(r.response.id)
|
|
196
|
+
return r.response.procedureCodes?.length === 1
|
|
197
|
+
&& r.response.procedureCodes?.[0]?.code === '99213'
|
|
198
|
+
&& r.response.diagnosisCodes?.length === 1
|
|
199
|
+
&& r.response.diagnosisCodes?.[0]?.code === 'E11.9'
|
|
200
|
+
} }
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
await async_test(
|
|
204
|
+
'persisted overridden FormResponse retains override codes',
|
|
205
|
+
() => sdk.api.form_responses.getOne(overridePreparedResponseId),
|
|
206
|
+
{ onResult: r => (
|
|
207
|
+
r.procedureCodes?.[0]?.code === '99213'
|
|
208
|
+
&& r.diagnosisCodes?.[0]?.code === 'E11.9'
|
|
209
|
+
) }
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// ---------- prepare with no Form defaults ----------
|
|
213
|
+
const formWithoutCodes = await sdk.api.forms.createOne({
|
|
214
|
+
title: 'EOM Procedure Codes Form (no codes)',
|
|
215
|
+
})
|
|
216
|
+
createdFormIds.push(formWithoutCodes.id)
|
|
217
|
+
|
|
218
|
+
await async_test(
|
|
219
|
+
'prepare_form_response with args on a Form lacking codes uses the args',
|
|
220
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
221
|
+
formId: formWithoutCodes.id,
|
|
222
|
+
enduserId: enduser.id,
|
|
223
|
+
procedureCodes: overrideProcedureCodes,
|
|
224
|
+
diagnosisCodes: overrideDiagnosisCodes,
|
|
225
|
+
}),
|
|
226
|
+
{ onResult: r => {
|
|
227
|
+
createdFormResponseIds.push(r.response.id)
|
|
228
|
+
return r.response.procedureCodes?.[0]?.code === '99213'
|
|
229
|
+
&& r.response.diagnosisCodes?.[0]?.code === 'E11.9'
|
|
230
|
+
} }
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
// ---------- prepare with no defaults and no args yields no codes ----------
|
|
234
|
+
await async_test(
|
|
235
|
+
'prepare_form_response with no codes anywhere produces FormResponse without codes',
|
|
236
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
237
|
+
formId: formWithoutCodes.id,
|
|
238
|
+
enduserId: enduser.id,
|
|
239
|
+
}),
|
|
240
|
+
{ onResult: r => {
|
|
241
|
+
createdFormResponseIds.push(r.response.id)
|
|
242
|
+
return !r.response.procedureCodes && !r.response.diagnosisCodes
|
|
243
|
+
} }
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// ---------- prepare rejects bad code shapes ----------
|
|
247
|
+
await async_test(
|
|
248
|
+
'prepare_form_response rejects procedureCode with non-string code',
|
|
249
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
250
|
+
formId: formWithoutCodes.id,
|
|
251
|
+
enduserId: enduser.id,
|
|
252
|
+
procedureCodes: [{ code: 12345 as any, units: 1 }],
|
|
253
|
+
}),
|
|
254
|
+
{ shouldError: true, onError: () => true }
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
await async_test(
|
|
258
|
+
'prepare_form_response rejects procedureCode with negative units',
|
|
259
|
+
() => sdk.api.form_responses.prepare_form_response({
|
|
260
|
+
formId: formWithoutCodes.id,
|
|
261
|
+
enduserId: enduser.id,
|
|
262
|
+
procedureCodes: [{ code: 'G0019', units: -1 }],
|
|
263
|
+
}),
|
|
264
|
+
{ shouldError: true, onError: () => true }
|
|
265
|
+
)
|
|
266
|
+
} finally {
|
|
267
|
+
for (const id of createdFormResponseIds) {
|
|
268
|
+
await sdk.api.form_responses.deleteOne(id).catch(() => {})
|
|
269
|
+
}
|
|
270
|
+
for (const id of createdFormIds) {
|
|
271
|
+
await sdk.api.forms.deleteOne(id).catch(() => {})
|
|
272
|
+
}
|
|
273
|
+
await sdk.api.endusers.deleteOne(enduser.id).catch(() => {})
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (require.main === module) {
|
|
278
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
279
|
+
const sdk = new Session({ host })
|
|
280
|
+
const sdkNonAdmin = new Session({ host })
|
|
281
|
+
|
|
282
|
+
const runTests = async () => {
|
|
283
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
284
|
+
await eom_procedure_codes_tests({ sdk, sdkNonAdmin })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
runTests()
|
|
288
|
+
.then(() => {
|
|
289
|
+
console.log("✅ EOM procedure codes test suite completed successfully")
|
|
290
|
+
process.exit(0)
|
|
291
|
+
})
|
|
292
|
+
.catch((error) => {
|
|
293
|
+
console.error("❌ EOM procedure codes test suite failed:", error)
|
|
294
|
+
process.exit(1)
|
|
295
|
+
})
|
|
296
|
+
}
|