@tellescope/sdk 1.246.2 → 1.248.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/sdk.d.ts +7 -1
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +2 -0
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/api_tests/date_string_validation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.js +142 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js +243 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.d.ts +13 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.js +818 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.js +429 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js +273 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.js +370 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +108 -24
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +303 -180
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/sdk.d.ts +7 -1
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +2 -0
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/tests/api_tests/date_string_validation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.js +138 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js +239 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/field_redaction.test.d.ts +13 -0
- package/lib/esm/tests/api_tests/field_redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/field_redaction.test.js +814 -0
- package/lib/esm/tests/api_tests/field_redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.js +425 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.js.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js +269 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.js +366 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.js.map +1 -0
- package/lib/esm/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js +108 -24
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +303 -180
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/sdk.ts +11 -0
- package/src/tests/api_tests/calendar_events_bulk_update.test.ts +418 -0
- package/src/tests/api_tests/date_string_validation.test.ts +107 -0
- package/src/tests/api_tests/enduser_session_invalidation.test.ts +138 -0
- package/src/tests/api_tests/field_redaction.test.ts +669 -0
- package/src/tests/api_tests/form_started_trigger.test.ts +1 -1
- package/src/tests/api_tests/form_submitted_trigger.test.ts +281 -0
- package/src/tests/api_tests/integrations_redacted.test.ts +245 -0
- package/src/tests/api_tests/mdb_sort.test.ts +259 -0
- package/src/tests/api_tests/openloop_webhooks.test.ts +64 -0
- package/src/tests/api_tests/organization_settings_duplicates.test.ts +201 -0
- package/src/tests/tests.ts +92 -6
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
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
|
+
|
|
11
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
12
|
+
|
|
13
|
+
// Main test function that can be called independently
|
|
14
|
+
export const mdb_sort_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
15
|
+
log_header("mdbSort Custom Sorting Support")
|
|
16
|
+
|
|
17
|
+
// Create test endusers with known field values for sorting
|
|
18
|
+
const testEndusers = await Promise.all([
|
|
19
|
+
sdk.api.endusers.createOne({ fname: 'Alice', lname: 'Smith', email: 'alice-mdbsort@tellescope.com' }),
|
|
20
|
+
sdk.api.endusers.createOne({ fname: 'Bob', lname: 'Jones', email: 'bob-mdbsort@tellescope.com' }),
|
|
21
|
+
sdk.api.endusers.createOne({ fname: 'Charlie', lname: 'Adams', email: 'charlie-mdbsort@tellescope.com' }),
|
|
22
|
+
sdk.api.endusers.createOne({ fname: 'Alice', lname: 'Zeta', email: 'alice2-mdbsort@tellescope.com' }), // Same fname for multi-field test
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
const enduserIds = testEndusers.map(e => e.id)
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Test 1: Sort by fname ascending (alphabetical order)
|
|
29
|
+
await async_test(
|
|
30
|
+
'mdbSort-fname-ascending',
|
|
31
|
+
async () => {
|
|
32
|
+
const results = await sdk.api.endusers.getSome({
|
|
33
|
+
filter: { id: { _in: enduserIds } },
|
|
34
|
+
mdbSort: { fname: 1 },
|
|
35
|
+
})
|
|
36
|
+
assert(results.length === 4, 'Expected 4 endusers', `Got ${results.length}`)
|
|
37
|
+
|
|
38
|
+
// Verify alphabetical order: Alice, Alice, Bob, Charlie
|
|
39
|
+
assert(results[0].fname === 'Alice', 'First should be Alice')
|
|
40
|
+
assert(results[1].fname === 'Alice', 'Second should be Alice')
|
|
41
|
+
assert(results[2].fname === 'Bob', 'Third should be Bob')
|
|
42
|
+
assert(results[3].fname === 'Charlie', 'Fourth should be Charlie')
|
|
43
|
+
|
|
44
|
+
return results
|
|
45
|
+
},
|
|
46
|
+
{ onResult: () => true },
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Test 2: Sort by fname descending (reverse alphabetical order)
|
|
50
|
+
await async_test(
|
|
51
|
+
'mdbSort-fname-descending',
|
|
52
|
+
async () => {
|
|
53
|
+
const results = await sdk.api.endusers.getSome({
|
|
54
|
+
filter: { id: { _in: enduserIds } },
|
|
55
|
+
mdbSort: { fname: -1 },
|
|
56
|
+
})
|
|
57
|
+
assert(results.length === 4, 'Expected 4 endusers')
|
|
58
|
+
|
|
59
|
+
// Verify reverse alphabetical order: Charlie, Bob, Alice, Alice
|
|
60
|
+
assert(results[0].fname === 'Charlie', 'First should be Charlie')
|
|
61
|
+
assert(results[1].fname === 'Bob', 'Second should be Bob')
|
|
62
|
+
assert(results[2].fname === 'Alice', 'Third should be Alice')
|
|
63
|
+
assert(results[3].fname === 'Alice', 'Fourth should be Alice')
|
|
64
|
+
|
|
65
|
+
return results
|
|
66
|
+
},
|
|
67
|
+
{ onResult: () => true },
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Test 3: Multi-field sort (fname ascending, then lname ascending for ties)
|
|
71
|
+
await async_test(
|
|
72
|
+
'mdbSort-multi-field',
|
|
73
|
+
async () => {
|
|
74
|
+
const results = await sdk.api.endusers.getSome({
|
|
75
|
+
filter: { id: { _in: enduserIds } },
|
|
76
|
+
mdbSort: { fname: 1, lname: 1 },
|
|
77
|
+
})
|
|
78
|
+
assert(results.length === 4, 'Expected 4 endusers')
|
|
79
|
+
|
|
80
|
+
// Verify multi-field sort:
|
|
81
|
+
// Alice Smith (fname: Alice, lname: Smith)
|
|
82
|
+
// Alice Zeta (fname: Alice, lname: Zeta)
|
|
83
|
+
// Bob Jones (fname: Bob)
|
|
84
|
+
// Charlie Adams (fname: Charlie)
|
|
85
|
+
assert(results[0].fname === 'Alice' && results[0].lname === 'Smith', 'First should be Alice Smith')
|
|
86
|
+
assert(results[1].fname === 'Alice' && results[1].lname === 'Zeta', 'Second should be Alice Zeta')
|
|
87
|
+
assert(results[2].fname === 'Bob', 'Third should be Bob')
|
|
88
|
+
assert(results[3].fname === 'Charlie', 'Fourth should be Charlie')
|
|
89
|
+
|
|
90
|
+
return results
|
|
91
|
+
},
|
|
92
|
+
{ onResult: () => true },
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// Test 4: mdbSort combined with mdbFilter
|
|
96
|
+
await async_test(
|
|
97
|
+
'mdbSort-with-mdbFilter',
|
|
98
|
+
async () => {
|
|
99
|
+
const results = await sdk.api.endusers.getSome({
|
|
100
|
+
mdbFilter: {
|
|
101
|
+
email: { $in: ['alice-mdbsort@tellescope.com', 'alice2-mdbsort@tellescope.com'] },
|
|
102
|
+
fname: 'Alice', // Only get Alice endusers
|
|
103
|
+
},
|
|
104
|
+
mdbSort: { lname: 1 }, // Sort by last name
|
|
105
|
+
})
|
|
106
|
+
assert(results.length === 2, 'Expected 2 Alice endusers', `Got ${results.length}`)
|
|
107
|
+
|
|
108
|
+
// Verify both are Alice and sorted by lname: Smith, then Zeta
|
|
109
|
+
assert(results[0].fname === 'Alice' && results[0].lname === 'Smith', 'First Alice should be Smith')
|
|
110
|
+
assert(results[1].fname === 'Alice' && results[1].lname === 'Zeta', 'Second Alice should be Zeta')
|
|
111
|
+
|
|
112
|
+
return results
|
|
113
|
+
},
|
|
114
|
+
{ onResult: () => true },
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Test 5: mdbSort keyset pagination via mdbFilter $or
|
|
118
|
+
// Note: filter is ignored when mdbFilter is present, so both id scoping and keyset cursor
|
|
119
|
+
// must be expressed in mdbFilter.
|
|
120
|
+
await async_test(
|
|
121
|
+
'mdbSort-keyset-pagination',
|
|
122
|
+
async () => {
|
|
123
|
+
const testEmails = [
|
|
124
|
+
'alice-mdbsort@tellescope.com',
|
|
125
|
+
'alice2-mdbsort@tellescope.com',
|
|
126
|
+
'bob-mdbsort@tellescope.com',
|
|
127
|
+
'charlie-mdbsort@tellescope.com',
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
// Page 1: first 2 results sorted by fname ascending → both Alices
|
|
131
|
+
const page1 = await sdk.api.endusers.getSome({
|
|
132
|
+
mdbFilter: { email: { $in: testEmails } },
|
|
133
|
+
mdbSort: { fname: 1 },
|
|
134
|
+
limit: 2,
|
|
135
|
+
})
|
|
136
|
+
assert(page1.length === 2, 'Expected 2 endusers on page 1')
|
|
137
|
+
assert(page1[0].fname === 'Alice', 'Page 1 first should be Alice')
|
|
138
|
+
assert(page1[1].fname === 'Alice', 'Page 1 second should be Alice')
|
|
139
|
+
|
|
140
|
+
// Page 2: keyset cursor — fname > last seen fname ('Alice')
|
|
141
|
+
const lastFname = page1[page1.length - 1].fname
|
|
142
|
+
const page2 = await sdk.api.endusers.getSome({
|
|
143
|
+
mdbFilter: {
|
|
144
|
+
email: { $in: testEmails },
|
|
145
|
+
fname: { $gt: lastFname },
|
|
146
|
+
},
|
|
147
|
+
mdbSort: { fname: 1 },
|
|
148
|
+
limit: 2,
|
|
149
|
+
})
|
|
150
|
+
assert(page2.length === 2, 'Expected 2 endusers on page 2')
|
|
151
|
+
assert(page2[0].fname === 'Bob', 'Page 2 first should be Bob')
|
|
152
|
+
assert(page2[1].fname === 'Charlie', 'Page 2 second should be Charlie')
|
|
153
|
+
|
|
154
|
+
return [...page1, ...page2]
|
|
155
|
+
},
|
|
156
|
+
{ onResult: () => true },
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// Test 6: mdbSort with projection (ensure both work together)
|
|
160
|
+
await async_test(
|
|
161
|
+
'mdbSort-with-projection',
|
|
162
|
+
async () => {
|
|
163
|
+
const results = await sdk.api.endusers.getSome({
|
|
164
|
+
filter: { id: { _in: enduserIds } },
|
|
165
|
+
mdbSort: { fname: -1 },
|
|
166
|
+
projection: { fname: 1, lname: 1 },
|
|
167
|
+
})
|
|
168
|
+
assert(results.length === 4, 'Expected 4 endusers')
|
|
169
|
+
|
|
170
|
+
// Verify sort order (descending)
|
|
171
|
+
assert(results[0].fname === 'Charlie', 'First should be Charlie')
|
|
172
|
+
|
|
173
|
+
// Verify projection (only fname, lname, plus id and createdAt)
|
|
174
|
+
assert(results[0].fname !== undefined, 'fname should be present')
|
|
175
|
+
assert(results[0].lname !== undefined, 'lname should be present')
|
|
176
|
+
assert((results[0] as any).email === undefined, 'email should NOT be present')
|
|
177
|
+
|
|
178
|
+
return results
|
|
179
|
+
},
|
|
180
|
+
{ onResult: () => true },
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
// Test 7: Non-admin access with mdbSort (RBA still applies)
|
|
184
|
+
await async_test(
|
|
185
|
+
'non-admin-mdbSort',
|
|
186
|
+
async () => {
|
|
187
|
+
const results = await sdkNonAdmin.api.endusers.getSome({
|
|
188
|
+
filter: { id: { _in: enduserIds } },
|
|
189
|
+
mdbSort: { fname: 1 },
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Non-admin should still get results (RBA should apply)
|
|
193
|
+
assert(Array.isArray(results), 'Non-admin should receive an array response')
|
|
194
|
+
|
|
195
|
+
// Verify sort order
|
|
196
|
+
if (results.length >= 2) {
|
|
197
|
+
const fnames = results.map(e => e.fname)
|
|
198
|
+
// Should be sorted alphabetically
|
|
199
|
+
assert(fnames[0]! <= fnames[1]!, 'Non-admin results should be sorted')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return results
|
|
203
|
+
},
|
|
204
|
+
{ onResult: () => true },
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// Test 8: mdbSort fallback behavior (no mdbSort uses sortBy default)
|
|
208
|
+
await async_test(
|
|
209
|
+
'no-mdbSort-fallback',
|
|
210
|
+
async () => {
|
|
211
|
+
// Without mdbSort, should fall back to default sorting by _id
|
|
212
|
+
const results = await sdk.api.endusers.getSome({
|
|
213
|
+
filter: { id: { _in: enduserIds } },
|
|
214
|
+
sortBy: 'updatedAt',
|
|
215
|
+
sort: 'oldFirst',
|
|
216
|
+
})
|
|
217
|
+
assert(results.length === 4, 'Expected 4 endusers')
|
|
218
|
+
|
|
219
|
+
// Verify traditional sortBy still works when mdbSort not provided
|
|
220
|
+
assert(results[0].id !== undefined, 'Should return valid endusers')
|
|
221
|
+
|
|
222
|
+
return results
|
|
223
|
+
},
|
|
224
|
+
{ onResult: () => true },
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
} finally {
|
|
228
|
+
// Cleanup: Delete test resources
|
|
229
|
+
try {
|
|
230
|
+
for (const enduserId of enduserIds) {
|
|
231
|
+
await sdk.api.endusers.deleteOne(enduserId)
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('Cleanup error:', error)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Allow running this test file independently
|
|
240
|
+
if (require.main === module) {
|
|
241
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
242
|
+
const sdk = new Session({ host })
|
|
243
|
+
const sdkNonAdmin = new Session({ host })
|
|
244
|
+
|
|
245
|
+
const runTests = async () => {
|
|
246
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
247
|
+
await mdb_sort_tests({ sdk, sdkNonAdmin })
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
runTests()
|
|
251
|
+
.then(() => {
|
|
252
|
+
console.log("✅ mdbSort test suite completed successfully")
|
|
253
|
+
process.exit(0)
|
|
254
|
+
})
|
|
255
|
+
.catch((error) => {
|
|
256
|
+
console.error("❌ mdbSort test suite failed:", error)
|
|
257
|
+
process.exit(1)
|
|
258
|
+
})
|
|
259
|
+
}
|
|
@@ -144,6 +144,27 @@ export const openloop_webhooks_tests = async ({ sdk, sdkNonAdmin }: { sdk: Sessi
|
|
|
144
144
|
{ onResult: (r: boolean) => r === true }
|
|
145
145
|
)
|
|
146
146
|
|
|
147
|
+
await async_test(
|
|
148
|
+
'V1: order_confirmation maps program_code to protocol field',
|
|
149
|
+
async () => {
|
|
150
|
+
const orderNum = `ol-conf-protocol-${uid()}`
|
|
151
|
+
const res = await postV1(makeV1Confirmation({
|
|
152
|
+
patientID: healthieId1,
|
|
153
|
+
orderNumber: orderNum,
|
|
154
|
+
program_code: 'Weight-Loss',
|
|
155
|
+
}))
|
|
156
|
+
assert(res.status === 200, `Expected 200, got ${res.status}`)
|
|
157
|
+
|
|
158
|
+
const orders = await sdk.api.enduser_orders.getSome({
|
|
159
|
+
filter: { source: 'OpenLoop', externalId: orderNum }
|
|
160
|
+
})
|
|
161
|
+
assert(orders.length === 1, `Expected 1 order, got ${orders.length}`)
|
|
162
|
+
assert(orders[0].protocol === 'Weight-Loss', `protocol mismatch: ${orders[0].protocol}`)
|
|
163
|
+
return true
|
|
164
|
+
},
|
|
165
|
+
{ onResult: (r: boolean) => r === true }
|
|
166
|
+
)
|
|
167
|
+
|
|
147
168
|
await async_test(
|
|
148
169
|
'V1: order_confirmation idempotency - same order not duplicated',
|
|
149
170
|
async () => {
|
|
@@ -314,6 +335,28 @@ export const openloop_webhooks_tests = async ({ sdk, sdkNonAdmin }: { sdk: Sessi
|
|
|
314
335
|
{ onResult: (r: boolean) => r === true }
|
|
315
336
|
)
|
|
316
337
|
|
|
338
|
+
await async_test(
|
|
339
|
+
'V1: order_shipped maps program_code to protocol field',
|
|
340
|
+
async () => {
|
|
341
|
+
const orderNum = `ol-ship-protocol-${uid()}`
|
|
342
|
+
await postV1(makeV1Confirmation({ patientID: healthieId1, orderNumber: orderNum }))
|
|
343
|
+
const res = await postV1(makeV1Shipped({
|
|
344
|
+
patientID: healthieId1,
|
|
345
|
+
orderNumber: orderNum,
|
|
346
|
+
program_code: 'Diabetes',
|
|
347
|
+
}))
|
|
348
|
+
assert(res.status === 200, `Expected 200, got ${res.status}`)
|
|
349
|
+
|
|
350
|
+
const orders = await sdk.api.enduser_orders.getSome({
|
|
351
|
+
filter: { source: 'OpenLoop', externalId: orderNum }
|
|
352
|
+
})
|
|
353
|
+
assert(orders.length === 1, `Expected 1 order, got ${orders.length}`)
|
|
354
|
+
assert(orders[0].protocol === 'Diabetes', `protocol mismatch: ${orders[0].protocol}`)
|
|
355
|
+
return true
|
|
356
|
+
},
|
|
357
|
+
{ onResult: (r: boolean) => r === true }
|
|
358
|
+
)
|
|
359
|
+
|
|
317
360
|
// ===== SECTION D: V1 enduserId Isolation =====
|
|
318
361
|
log_header("V1 enduserId Isolation")
|
|
319
362
|
|
|
@@ -414,6 +457,27 @@ export const openloop_webhooks_tests = async ({ sdk, sdkNonAdmin }: { sdk: Sessi
|
|
|
414
457
|
{ onResult: (r: boolean) => r === true }
|
|
415
458
|
)
|
|
416
459
|
|
|
460
|
+
await async_test(
|
|
461
|
+
'V2: prescription-created maps program_code to protocol field',
|
|
462
|
+
async () => {
|
|
463
|
+
const orderNum = `v2-protocol-${uid()}`
|
|
464
|
+
const res = await postV2(makeV2Payload('prescription-created', {
|
|
465
|
+
id: orderNum,
|
|
466
|
+
patientId: healthieId1,
|
|
467
|
+
program_code: 'GLP-1',
|
|
468
|
+
}))
|
|
469
|
+
assert(res.status === 200, `Expected 200, got ${res.status}`)
|
|
470
|
+
|
|
471
|
+
const orders = await sdk.api.enduser_orders.getSome({
|
|
472
|
+
filter: { source: 'OpenLoop', externalId: orderNum }
|
|
473
|
+
})
|
|
474
|
+
assert(orders.length === 1, `Expected 1 order, got ${orders.length}`)
|
|
475
|
+
assert(orders[0].protocol === 'GLP-1', `protocol mismatch: ${orders[0].protocol}`)
|
|
476
|
+
return true
|
|
477
|
+
},
|
|
478
|
+
{ onResult: (r: boolean) => r === true }
|
|
479
|
+
)
|
|
480
|
+
|
|
417
481
|
await async_test(
|
|
418
482
|
'V2: prescription-shipped updates with carrier and tracking',
|
|
419
483
|
async () => {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
async_test,
|
|
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
|
+
export const organization_settings_duplicates_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
13
|
+
log_header("Organization Settings Duplicate Validation Tests")
|
|
14
|
+
|
|
15
|
+
const orgId = sdk.userInfo.businessId
|
|
16
|
+
|
|
17
|
+
// === A. replaceObjectFields: false (merge/push behavior) ===
|
|
18
|
+
|
|
19
|
+
// A1. Duplicate tags via merge
|
|
20
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
21
|
+
settings: { endusers: { tags: ['tag1', 'tag2'] } }
|
|
22
|
+
}, { replaceObjectFields: true })
|
|
23
|
+
|
|
24
|
+
await async_test(
|
|
25
|
+
"Merge tags rejects duplicates (tag2 appears in both old and new)",
|
|
26
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
27
|
+
settings: { endusers: { tags: ['tag2', 'tag3'] } }
|
|
28
|
+
}),
|
|
29
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate value in settings.endusers.tags') }
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// A2. Duplicate customFields via merge
|
|
33
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
34
|
+
settings: { endusers: { customFields: [{ type: 'Text' as const, field: 'myField', info: {} }] } }
|
|
35
|
+
}, { replaceObjectFields: true })
|
|
36
|
+
|
|
37
|
+
await async_test(
|
|
38
|
+
"Merge customFields rejects duplicate field name",
|
|
39
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
40
|
+
settings: { endusers: { customFields: [{ type: 'Text' as const, field: 'myField', info: {} }] } }
|
|
41
|
+
}),
|
|
42
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate field in settings.endusers.customFields') }
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// A3. Duplicate builtinFields via merge
|
|
46
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
47
|
+
settings: { endusers: { builtinFields: [{ field: 'fname', label: 'First Name' }] } }
|
|
48
|
+
}, { replaceObjectFields: true })
|
|
49
|
+
|
|
50
|
+
await async_test(
|
|
51
|
+
"Merge builtinFields rejects duplicate field name",
|
|
52
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
53
|
+
settings: { endusers: { builtinFields: [{ field: 'fname', label: 'First Name Copy' }] } }
|
|
54
|
+
}),
|
|
55
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate field in settings.endusers.builtinFields') }
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// A4. Duplicate dontRecordCallsToPhone via merge
|
|
59
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
60
|
+
settings: { endusers: { dontRecordCallsToPhone: ['+15551234567'] } }
|
|
61
|
+
}, { replaceObjectFields: true })
|
|
62
|
+
|
|
63
|
+
await async_test(
|
|
64
|
+
"Merge dontRecordCallsToPhone rejects duplicates",
|
|
65
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
66
|
+
settings: { endusers: { dontRecordCallsToPhone: ['+15551234567'] } }
|
|
67
|
+
}),
|
|
68
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate value in settings.endusers.dontRecordCallsToPhone') }
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// A5. Duplicate cancelReasons via merge
|
|
72
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
73
|
+
settings: { calendar: { cancelReasons: ['No show'] } }
|
|
74
|
+
}, { replaceObjectFields: true })
|
|
75
|
+
|
|
76
|
+
await async_test(
|
|
77
|
+
"Merge cancelReasons rejects duplicates",
|
|
78
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
79
|
+
settings: { calendar: { cancelReasons: ['No show'] } }
|
|
80
|
+
}),
|
|
81
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate value in settings.calendar.cancelReasons') }
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// === B. replaceObjectFields: true (full replacement) ===
|
|
85
|
+
|
|
86
|
+
// B1. Replace that grows the array with dupes should be rejected
|
|
87
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
88
|
+
settings: { endusers: { tags: ['tag1'] } }
|
|
89
|
+
}, { replaceObjectFields: true })
|
|
90
|
+
|
|
91
|
+
await async_test(
|
|
92
|
+
"Replace tags rejects duplicates when array grows",
|
|
93
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
94
|
+
settings: { endusers: { tags: ['tag1', 'tag1', 'tag2'] } }
|
|
95
|
+
}, { replaceObjectFields: true }),
|
|
96
|
+
{ shouldError: true, onError: (e: { message: string }) => e.message.includes('Duplicate value in settings.endusers.tags') }
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// B2. Replace with dupes that shrinks the array should be allowed
|
|
100
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
101
|
+
settings: { endusers: { tags: ['a', 'b', 'c'] } }
|
|
102
|
+
}, { replaceObjectFields: true })
|
|
103
|
+
|
|
104
|
+
await async_test(
|
|
105
|
+
"Replace with dupes that shrinks array succeeds",
|
|
106
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
107
|
+
settings: { endusers: { tags: ['a', 'a'] } }
|
|
108
|
+
}, { replaceObjectFields: true }),
|
|
109
|
+
{ shouldError: false, onResult: () => true }
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// B3. Replace with unique values always succeeds
|
|
113
|
+
await async_test(
|
|
114
|
+
"Replace tags succeeds with unique values",
|
|
115
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
116
|
+
settings: { endusers: { tags: ['tag1', 'tag2'] } }
|
|
117
|
+
}, { replaceObjectFields: true }),
|
|
118
|
+
{ shouldError: false, onResult: () => true }
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// === C. Non-duplicate updates still succeed ===
|
|
122
|
+
|
|
123
|
+
// C1. Set initial tags then add different tags via merge
|
|
124
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
125
|
+
settings: { endusers: { tags: ['tagA', 'tagB'] } }
|
|
126
|
+
}, { replaceObjectFields: true })
|
|
127
|
+
|
|
128
|
+
await async_test(
|
|
129
|
+
"Merge with unique new tags succeeds",
|
|
130
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
131
|
+
settings: { endusers: { tags: ['tagC', 'tagD'] } }
|
|
132
|
+
}),
|
|
133
|
+
{ shouldError: false, onResult: () => true }
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// C2. Replace with unique values
|
|
137
|
+
await async_test(
|
|
138
|
+
"Replace customFields with unique values succeeds",
|
|
139
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
140
|
+
settings: { endusers: { customFields: [
|
|
141
|
+
{ type: 'Text' as const, field: 'field1', info: {} },
|
|
142
|
+
{ type: 'Text' as const, field: 'field2', info: {} },
|
|
143
|
+
] } }
|
|
144
|
+
}, { replaceObjectFields: true }),
|
|
145
|
+
{ shouldError: false, onResult: () => true }
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// C3. Updating another settings field preserves pre-existing duplicates
|
|
149
|
+
// First set tags to a longer array, then shrink to a dupe array (shrinking is allowed)
|
|
150
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
151
|
+
settings: { endusers: { tags: ['dupeTag', 'otherTag', 'anotherTag'] } }
|
|
152
|
+
}, { replaceObjectFields: true })
|
|
153
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
154
|
+
settings: { endusers: { tags: ['dupeTag', 'dupeTag'] } }
|
|
155
|
+
}, { replaceObjectFields: true })
|
|
156
|
+
|
|
157
|
+
await async_test(
|
|
158
|
+
"Updating cancelReasons succeeds even when tags has pre-existing duplicates",
|
|
159
|
+
() => sdk.api.organizations.updateOne(orgId, {
|
|
160
|
+
settings: { calendar: { cancelReasons: ['new reason'] } }
|
|
161
|
+
}, { replaceObjectFields: true }),
|
|
162
|
+
{ shouldError: false, onResult: () => true }
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// Clean up settings to avoid affecting other tests
|
|
166
|
+
await sdk.api.organizations.updateOne(orgId, {
|
|
167
|
+
settings: {
|
|
168
|
+
endusers: {
|
|
169
|
+
tags: [],
|
|
170
|
+
customFields: [],
|
|
171
|
+
builtinFields: [],
|
|
172
|
+
dontRecordCallsToPhone: [],
|
|
173
|
+
},
|
|
174
|
+
calendar: {
|
|
175
|
+
cancelReasons: [],
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
}, { replaceObjectFields: true })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Allow running this test file independently
|
|
182
|
+
if (require.main === module) {
|
|
183
|
+
console.log(`Using API URL: ${host}`)
|
|
184
|
+
const sdk = new Session({ host })
|
|
185
|
+
const sdkNonAdmin = new Session({ host })
|
|
186
|
+
|
|
187
|
+
const runTests = async () => {
|
|
188
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
189
|
+
await organization_settings_duplicates_tests({ sdk, sdkNonAdmin })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
runTests()
|
|
193
|
+
.then(() => {
|
|
194
|
+
console.log("✅ Organization settings duplicate validation tests completed successfully")
|
|
195
|
+
process.exit(0)
|
|
196
|
+
})
|
|
197
|
+
.catch((error) => {
|
|
198
|
+
console.error("❌ Organization settings duplicate validation tests failed:", error)
|
|
199
|
+
process.exit(1)
|
|
200
|
+
})
|
|
201
|
+
}
|