@tellescope/sdk 1.244.1 → 1.244.2
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 +3 -0
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/api_tests/elation_user_id.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/elation_user_id.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/elation_user_id.test.js +106 -0
- package/lib/cjs/tests/api_tests/elation_user_id.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/get_some_projection.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/get_some_projection.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/get_some_projection.test.js +373 -0
- package/lib/cjs/tests/api_tests/get_some_projection.test.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +169 -133
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/sdk.d.ts +5 -2
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/tests/api_tests/elation_user_id.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/elation_user_id.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/elation_user_id.test.js +102 -0
- package/lib/esm/tests/api_tests/elation_user_id.test.js.map +1 -0
- package/lib/esm/tests/api_tests/get_some_projection.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/get_some_projection.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/get_some_projection.test.js +369 -0
- package/lib/esm/tests/api_tests/get_some_projection.test.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +169 -133
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/sdk.ts +5 -4
- package/src/tests/api_tests/elation_user_id.test.ts +59 -0
- package/src/tests/api_tests/get_some_projection.test.ts +245 -0
- package/src/tests/tests.ts +71 -8
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
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 get_some_projection_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
15
|
+
log_header("getSome Projection Support")
|
|
16
|
+
|
|
17
|
+
// Create test enduser
|
|
18
|
+
const testEnduser = await sdk.api.endusers.createOne({ fname: 'ProjectionTest', lname: 'User', email: 'projection-test@tellescope.com' })
|
|
19
|
+
|
|
20
|
+
let observationIds: string[] = []
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Create test observations
|
|
24
|
+
const obs1 = await sdk.api.enduser_observations.createOne({
|
|
25
|
+
enduserId: testEnduser.id,
|
|
26
|
+
status: 'final',
|
|
27
|
+
category: 'vital-signs',
|
|
28
|
+
measurement: { value: 120, unit: 'mmHg' },
|
|
29
|
+
})
|
|
30
|
+
const obs2 = await sdk.api.enduser_observations.createOne({
|
|
31
|
+
enduserId: testEnduser.id,
|
|
32
|
+
status: 'final',
|
|
33
|
+
category: 'vital-signs',
|
|
34
|
+
measurement: { value: 80, unit: 'mmHg' },
|
|
35
|
+
})
|
|
36
|
+
const obs3 = await sdk.api.enduser_observations.createOne({
|
|
37
|
+
enduserId: testEnduser.id,
|
|
38
|
+
status: 'final',
|
|
39
|
+
category: 'vital-signs',
|
|
40
|
+
measurement: { value: 98, unit: 'bpm' },
|
|
41
|
+
})
|
|
42
|
+
observationIds = [obs1.id, obs2.id, obs3.id]
|
|
43
|
+
|
|
44
|
+
// Test 1: Inclusion projection returns only specified fields
|
|
45
|
+
await async_test(
|
|
46
|
+
'projection-inclusion',
|
|
47
|
+
async () => {
|
|
48
|
+
const results = await sdk.api.enduser_observations.getSome({
|
|
49
|
+
filter: { enduserId: testEnduser.id },
|
|
50
|
+
projection: { timestamp: 1, measurement: 1, enduserId: 1 },
|
|
51
|
+
})
|
|
52
|
+
assert(results.length === 3, 'Expected 3 observations with projection', 'Got correct count with projection')
|
|
53
|
+
|
|
54
|
+
for (const r of results) {
|
|
55
|
+
assert(r.id !== undefined, 'id should always be present')
|
|
56
|
+
assert(r.createdAt !== undefined, 'createdAt should always be present')
|
|
57
|
+
assert(r.measurement !== undefined, 'measurement should be present in projection')
|
|
58
|
+
assert(r.enduserId !== undefined, 'enduserId should be present in projection')
|
|
59
|
+
assert(r.timestamp !== undefined, 'timestamp should be present in projection')
|
|
60
|
+
// Fields NOT in projection should be excluded
|
|
61
|
+
assert((r as any).status === undefined, 'status should NOT be present when not in projection')
|
|
62
|
+
assert((r as any).category === undefined, 'category should NOT be present when not in projection')
|
|
63
|
+
}
|
|
64
|
+
return results
|
|
65
|
+
},
|
|
66
|
+
{ onResult: () => true },
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// Test 2: No projection returns all fields (backward compatibility)
|
|
70
|
+
await async_test(
|
|
71
|
+
'no-projection-returns-all-fields',
|
|
72
|
+
async () => {
|
|
73
|
+
const results = await sdk.api.enduser_observations.getSome({
|
|
74
|
+
filter: { enduserId: testEnduser.id },
|
|
75
|
+
})
|
|
76
|
+
assert(results.length === 3, 'Expected 3 observations without projection')
|
|
77
|
+
|
|
78
|
+
for (const r of results) {
|
|
79
|
+
assert(r.id !== undefined, 'id should be present')
|
|
80
|
+
assert(r.status !== undefined, 'status should be present without projection')
|
|
81
|
+
assert(r.category !== undefined, 'category should be present without projection')
|
|
82
|
+
assert(r.measurement !== undefined, 'measurement should be present without projection')
|
|
83
|
+
assert(r.enduserId !== undefined, 'enduserId should be present without projection')
|
|
84
|
+
}
|
|
85
|
+
return results
|
|
86
|
+
},
|
|
87
|
+
{ onResult: () => true },
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Test 3: Projection with other filters (filter, limit, sortBy)
|
|
91
|
+
await async_test(
|
|
92
|
+
'projection-with-filters',
|
|
93
|
+
async () => {
|
|
94
|
+
const results = await sdk.api.enduser_observations.getSome({
|
|
95
|
+
filter: { enduserId: testEnduser.id },
|
|
96
|
+
projection: { measurement: 1 },
|
|
97
|
+
limit: 2,
|
|
98
|
+
sortBy: 'timestamp',
|
|
99
|
+
})
|
|
100
|
+
assert(results.length === 2, 'Expected 2 observations with limit=2', `Got ${results.length}`)
|
|
101
|
+
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
assert(r.measurement !== undefined, 'measurement should be present in projection')
|
|
104
|
+
assert((r as any).status === undefined, 'status should NOT be present when not in projection')
|
|
105
|
+
}
|
|
106
|
+
return results
|
|
107
|
+
},
|
|
108
|
+
{ onResult: () => true },
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Test 4: Projection works across different models (endusers)
|
|
112
|
+
await async_test(
|
|
113
|
+
'projection-on-endusers',
|
|
114
|
+
async () => {
|
|
115
|
+
const results = await sdk.api.endusers.getSome({
|
|
116
|
+
filter: { email: 'projection-test@tellescope.com' },
|
|
117
|
+
projection: { fname: 1, email: 1 },
|
|
118
|
+
})
|
|
119
|
+
assert(results.length >= 1, 'Expected at least 1 enduser')
|
|
120
|
+
|
|
121
|
+
const enduser = results[0]
|
|
122
|
+
assert(enduser.id !== undefined, 'id should always be present')
|
|
123
|
+
assert(enduser.fname !== undefined, 'fname should be present in projection')
|
|
124
|
+
assert(enduser.email !== undefined, 'email should be present in projection')
|
|
125
|
+
assert((enduser as any).lname === undefined, 'lname should NOT be present when not in projection')
|
|
126
|
+
return results
|
|
127
|
+
},
|
|
128
|
+
{ onResult: () => true },
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
// Test 5: Projection with pagination (lastId)
|
|
132
|
+
await async_test(
|
|
133
|
+
'projection-with-pagination',
|
|
134
|
+
async () => {
|
|
135
|
+
// First page
|
|
136
|
+
const page1 = await sdk.api.enduser_observations.getSome({
|
|
137
|
+
filter: { enduserId: testEnduser.id },
|
|
138
|
+
projection: { measurement: 1, enduserId: 1 },
|
|
139
|
+
limit: 2,
|
|
140
|
+
sort: 'oldFirst',
|
|
141
|
+
})
|
|
142
|
+
assert(page1.length === 2, 'Expected 2 observations on first page')
|
|
143
|
+
|
|
144
|
+
// Second page using lastId from first page
|
|
145
|
+
const lastId = page1[page1.length - 1].id
|
|
146
|
+
const page2 = await sdk.api.enduser_observations.getSome({
|
|
147
|
+
filter: { enduserId: testEnduser.id },
|
|
148
|
+
projection: { measurement: 1, enduserId: 1 },
|
|
149
|
+
limit: 2,
|
|
150
|
+
sort: 'oldFirst',
|
|
151
|
+
lastId,
|
|
152
|
+
})
|
|
153
|
+
assert(page2.length === 1, 'Expected 1 observation on second page')
|
|
154
|
+
|
|
155
|
+
// Verify consistent fields across pages
|
|
156
|
+
for (const r of [...page1, ...page2]) {
|
|
157
|
+
assert(r.measurement !== undefined, 'measurement should be present across pages')
|
|
158
|
+
assert(r.enduserId !== undefined, 'enduserId should be present across pages')
|
|
159
|
+
assert((r as any).status === undefined, 'status should NOT be present across pages')
|
|
160
|
+
}
|
|
161
|
+
return [...page1, ...page2]
|
|
162
|
+
},
|
|
163
|
+
{ onResult: () => true },
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
// Test 6: Empty projection returns all fields (no crash)
|
|
167
|
+
await async_test(
|
|
168
|
+
'empty-projection',
|
|
169
|
+
async () => {
|
|
170
|
+
const results = await sdk.api.enduser_observations.getSome({
|
|
171
|
+
filter: { enduserId: testEnduser.id },
|
|
172
|
+
projection: {},
|
|
173
|
+
})
|
|
174
|
+
assert(results.length === 3, 'Expected 3 observations with empty projection')
|
|
175
|
+
|
|
176
|
+
for (const r of results) {
|
|
177
|
+
assert(r.status !== undefined, 'status should be present with empty projection')
|
|
178
|
+
assert(r.category !== undefined, 'category should be present with empty projection')
|
|
179
|
+
assert(r.measurement !== undefined, 'measurement should be present with empty projection')
|
|
180
|
+
}
|
|
181
|
+
return results
|
|
182
|
+
},
|
|
183
|
+
{ onResult: () => true },
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Test 7: Non-admin access with projection (RBA still applies)
|
|
187
|
+
await async_test(
|
|
188
|
+
'non-admin-projection',
|
|
189
|
+
async () => {
|
|
190
|
+
const results = await sdkNonAdmin.api.enduser_observations.getSome({
|
|
191
|
+
filter: { enduserId: testEnduser.id },
|
|
192
|
+
projection: { measurement: 1, enduserId: 1 },
|
|
193
|
+
})
|
|
194
|
+
// Non-admin should still get results (RBA should apply)
|
|
195
|
+
assert(Array.isArray(results), 'Non-admin should receive an array response')
|
|
196
|
+
|
|
197
|
+
for (const r of results) {
|
|
198
|
+
assert(r.measurement !== undefined, 'measurement should be present for non-admin projection')
|
|
199
|
+
assert(r.enduserId !== undefined, 'enduserId should be present for non-admin projection')
|
|
200
|
+
}
|
|
201
|
+
return results
|
|
202
|
+
},
|
|
203
|
+
{ onResult: () => true },
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
} finally {
|
|
207
|
+
// Cleanup: Delete test resources
|
|
208
|
+
try {
|
|
209
|
+
for (const obsId of observationIds) {
|
|
210
|
+
await sdk.api.enduser_observations.deleteOne(obsId)
|
|
211
|
+
}
|
|
212
|
+
await sdk.api.endusers.deleteOne(testEnduser.id)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Cleanup error:', error)
|
|
215
|
+
// Try to at least delete the enduser (cascade deletes observations)
|
|
216
|
+
try {
|
|
217
|
+
await sdk.api.endusers.deleteOne(testEnduser.id)
|
|
218
|
+
} catch (deleteError) {
|
|
219
|
+
console.error('Failed to delete test enduser:', deleteError)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Allow running this test file independently
|
|
226
|
+
if (require.main === module) {
|
|
227
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
228
|
+
const sdk = new Session({ host })
|
|
229
|
+
const sdkNonAdmin = new Session({ host })
|
|
230
|
+
|
|
231
|
+
const runTests = async () => {
|
|
232
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
233
|
+
await get_some_projection_tests({ sdk, sdkNonAdmin })
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
runTests()
|
|
237
|
+
.then(() => {
|
|
238
|
+
console.log("✅ getSome projection test suite completed successfully")
|
|
239
|
+
process.exit(0)
|
|
240
|
+
})
|
|
241
|
+
.catch((error) => {
|
|
242
|
+
console.error("❌ getSome projection test suite failed:", error)
|
|
243
|
+
process.exit(1)
|
|
244
|
+
})
|
|
245
|
+
}
|
package/src/tests/tests.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
|
|
37
37
|
import { Session, APIQuery, EnduserSession } from "../sdk"
|
|
38
38
|
import { enduser_observations_acknowledge_tests } from "./api_tests/enduser_observations_acknowledge.test"
|
|
39
|
+
import { get_some_projection_tests } from "./api_tests/get_some_projection.test"
|
|
39
40
|
import { create_user_notifications_trigger_tests } from "./api_tests/create_user_notifications_trigger.test"
|
|
40
41
|
import { inbox_thread_assignment_updates_tests } from "./api_tests/inbox_thread_assignment_updates.test"
|
|
41
42
|
import { inbox_thread_draft_scheduled_tests } from "./api_tests/inbox_thread_draft_scheduled.test"
|
|
@@ -85,6 +86,7 @@ import { database_cascade_delete_tests } from "./api_tests/database_cascade_dele
|
|
|
85
86
|
import { ai_conversations_tests } from "./api_tests/ai_conversations.test";
|
|
86
87
|
import { load_team_chat_tests } from "./api_tests/load_team_chat.test";
|
|
87
88
|
import { form_started_trigger_tests } from "./api_tests/form_started_trigger.test";
|
|
89
|
+
import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
|
|
88
90
|
|
|
89
91
|
const UniquenessViolationMessage = 'Uniqueness Violation'
|
|
90
92
|
|
|
@@ -5572,28 +5574,87 @@ const redaction_tests = async () => {
|
|
|
5572
5574
|
log_header("Redaction")
|
|
5573
5575
|
|
|
5574
5576
|
const enduser = await sdk.api.endusers.createOne({ email })
|
|
5575
|
-
const enduserOther = await sdk.api.endusers.createOne({
|
|
5577
|
+
const enduserOther = await sdk.api.endusers.createOne({
|
|
5578
|
+
email: 'otherenduser@tellescope.com',
|
|
5579
|
+
unredactedFields: [{ field: 'testField', value: 'testValue' }] as any,
|
|
5580
|
+
})
|
|
5576
5581
|
await sdk.api.endusers.set_password({ id: enduser.id, password }).catch(console.error)
|
|
5577
|
-
await enduserSDK.authenticate(email, password).catch(console.error)
|
|
5582
|
+
await enduserSDK.authenticate(email, password).catch(console.error)
|
|
5578
5583
|
|
|
5579
5584
|
const endusers = await enduserSDK.api.endusers.getSome()
|
|
5580
5585
|
const forUser = await sdk.api.endusers.getSome()
|
|
5581
5586
|
assert(endusers.length > 0, "enduser can't fetch others", "enduser get others successful")
|
|
5582
5587
|
|
|
5588
|
+
// endusers reading OTHER endusers should only see id, displayName, unredactedFields, and unredactedTags
|
|
5589
|
+
const ALLOWED_OTHER_ENDUSER_FIELDS = ['id', 'displayName', 'unredactedFields', 'unredactedTags']
|
|
5590
|
+
const otherEndusers = endusers.filter(e => e.id !== enduser.id)
|
|
5591
|
+
assert(otherEndusers.length > 0, 'no other endusers found', 'found other endusers to check redaction')
|
|
5592
|
+
for (const other of otherEndusers) {
|
|
5593
|
+
const keys = Object.keys(other)
|
|
5594
|
+
assert(
|
|
5595
|
+
keys.every(k => ALLOWED_OTHER_ENDUSER_FIELDS.includes(k)),
|
|
5596
|
+
`other enduser has extra fields: ${keys.filter(k => !ALLOWED_OTHER_ENDUSER_FIELDS.includes(k)).join(', ')}`,
|
|
5597
|
+
'other enduser correctly redacted to only id, displayName, unredactedFields, unredactedTags',
|
|
5598
|
+
)
|
|
5599
|
+
assert(
|
|
5600
|
+
keys.includes('id'),
|
|
5601
|
+
`other enduser missing id`,
|
|
5602
|
+
'other enduser has id',
|
|
5603
|
+
)
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
// verify unredactedFields are preserved for other endusers when defined
|
|
5607
|
+
const otherWithUnredacted = otherEndusers.find(e => e.id === enduserOther.id)
|
|
5608
|
+
if (otherWithUnredacted) {
|
|
5609
|
+
assert(
|
|
5610
|
+
!!(otherWithUnredacted as any).unredactedFields?.length,
|
|
5611
|
+
'unredactedFields missing on other enduser',
|
|
5612
|
+
'unredactedFields preserved on other enduser when defined',
|
|
5613
|
+
)
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5616
|
+
// verify unredactedFields/unredactedTags are omitted when not set on the enduser
|
|
5617
|
+
const otherWithoutUnredacted = otherEndusers.find(e => e.id !== enduserOther.id)
|
|
5618
|
+
if (otherWithoutUnredacted) {
|
|
5619
|
+
assert(
|
|
5620
|
+
!('unredactedFields' in otherWithoutUnredacted),
|
|
5621
|
+
'unredactedFields present when not set',
|
|
5622
|
+
'unredactedFields omitted when not defined on enduser',
|
|
5623
|
+
)
|
|
5624
|
+
assert(
|
|
5625
|
+
!('unredactedTags' in otherWithoutUnredacted),
|
|
5626
|
+
'unredactedTags present when not set',
|
|
5627
|
+
'unredactedTags omitted when not defined on enduser',
|
|
5628
|
+
)
|
|
5629
|
+
}
|
|
5630
|
+
|
|
5631
|
+
// enduser reading their own profile should still have non-redacted fields (not just id + displayName)
|
|
5632
|
+
const selfEnduser = endusers.find(e => e.id === enduser.id)
|
|
5633
|
+
if (selfEnduser) {
|
|
5634
|
+
assert(
|
|
5635
|
+
Object.keys(selfEnduser).length > ALLOWED_OTHER_ENDUSER_FIELDS.length,
|
|
5636
|
+
'self enduser overly redacted',
|
|
5637
|
+
'self enduser retains non-redacted fields',
|
|
5638
|
+
)
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5641
|
+
// verify schema-level redactions still apply to self
|
|
5583
5642
|
const redactedFields = (
|
|
5584
5643
|
Object.keys(schema.endusers.fields)
|
|
5585
|
-
.filter(f =>
|
|
5644
|
+
.filter(f =>
|
|
5586
5645
|
schema.endusers.fields[f as keyof typeof schema.endusers.fields]?.redactions?.includes('enduser')
|
|
5587
5646
|
|| schema.endusers.fields[f as keyof typeof schema.endusers.fields]?.redactions?.includes('all')
|
|
5588
5647
|
)
|
|
5589
5648
|
)
|
|
5590
5649
|
assert(redactedFields.length > 0, 'no redacted fields', 'redacted fields exists')
|
|
5591
5650
|
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5651
|
+
if (selfEnduser) {
|
|
5652
|
+
assert(
|
|
5653
|
+
redactedFields.filter(f => !!(selfEnduser as any)[f]).length === 0,
|
|
5654
|
+
'self enduser got redacted data',
|
|
5655
|
+
'self enduser data correctly redacted per schema',
|
|
5656
|
+
)
|
|
5657
|
+
}
|
|
5597
5658
|
|
|
5598
5659
|
assert(
|
|
5599
5660
|
!forUser.find(u => u.hashedPassword),
|
|
@@ -13947,6 +14008,8 @@ const ip_address_form_tests = async () => {
|
|
|
13947
14008
|
await replace_enduser_template_values_tests()
|
|
13948
14009
|
await mfa_tests()
|
|
13949
14010
|
await setup_tests(sdk, sdkNonAdmin)
|
|
14011
|
+
await get_some_projection_tests({ sdk, sdkNonAdmin })
|
|
14012
|
+
await elation_user_id_tests({ sdk, sdkNonAdmin })
|
|
13950
14013
|
await custom_dashboards_tests({ sdk, sdkNonAdmin })
|
|
13951
14014
|
await concurrent_build_threads_tests({ sdk, sdkNonAdmin })
|
|
13952
14015
|
await custom_aggregation_tests({ sdk, sdkNonAdmin })
|
package/test_generated.pdf
CHANGED
|
Binary file
|