@tellescope/sdk 1.242.9 → 1.243.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.
Files changed (64) hide show
  1. package/lib/cjs/enduser.d.ts +20 -0
  2. package/lib/cjs/enduser.d.ts.map +1 -1
  3. package/lib/cjs/sdk.d.ts +45 -0
  4. package/lib/cjs/sdk.d.ts.map +1 -1
  5. package/lib/cjs/sdk.js +2 -0
  6. package/lib/cjs/sdk.js.map +1 -1
  7. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.d.ts +6 -0
  8. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.d.ts.map +1 -0
  9. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.js +169 -0
  10. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.js.map +1 -0
  11. package/lib/cjs/tests/api_tests/custom_aggregation.test.d.ts.map +1 -1
  12. package/lib/cjs/tests/api_tests/custom_aggregation.test.js +109 -7
  13. package/lib/cjs/tests/api_tests/custom_aggregation.test.js.map +1 -1
  14. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts +6 -0
  15. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts.map +1 -0
  16. package/lib/cjs/tests/api_tests/custom_dashboards.test.js +304 -0
  17. package/lib/cjs/tests/api_tests/custom_dashboards.test.js.map +1 -0
  18. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  19. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js +655 -139
  20. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  21. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.d.ts +20 -0
  22. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -0
  23. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js +481 -0
  24. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js.map +1 -0
  25. package/lib/cjs/tests/tests.d.ts.map +1 -1
  26. package/lib/cjs/tests/tests.js +125 -112
  27. package/lib/cjs/tests/tests.js.map +1 -1
  28. package/lib/esm/enduser.d.ts +20 -0
  29. package/lib/esm/enduser.d.ts.map +1 -1
  30. package/lib/esm/sdk.d.ts +45 -0
  31. package/lib/esm/sdk.d.ts.map +1 -1
  32. package/lib/esm/sdk.js +2 -0
  33. package/lib/esm/sdk.js.map +1 -1
  34. package/lib/esm/tests/api_tests/concurrent_build_threads.test.d.ts +6 -0
  35. package/lib/esm/tests/api_tests/concurrent_build_threads.test.d.ts.map +1 -0
  36. package/lib/esm/tests/api_tests/concurrent_build_threads.test.js +165 -0
  37. package/lib/esm/tests/api_tests/concurrent_build_threads.test.js.map +1 -0
  38. package/lib/esm/tests/api_tests/custom_aggregation.test.d.ts.map +1 -1
  39. package/lib/esm/tests/api_tests/custom_aggregation.test.js +110 -8
  40. package/lib/esm/tests/api_tests/custom_aggregation.test.js.map +1 -1
  41. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts +6 -0
  42. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts.map +1 -0
  43. package/lib/esm/tests/api_tests/custom_dashboards.test.js +300 -0
  44. package/lib/esm/tests/api_tests/custom_dashboards.test.js.map +1 -0
  45. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  46. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js +655 -139
  47. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  48. package/lib/esm/tests/api_tests/no_access_permission_checks.test.d.ts +20 -0
  49. package/lib/esm/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -0
  50. package/lib/esm/tests/api_tests/no_access_permission_checks.test.js +477 -0
  51. package/lib/esm/tests/api_tests/no_access_permission_checks.test.js.map +1 -0
  52. package/lib/esm/tests/tests.d.ts.map +1 -1
  53. package/lib/esm/tests/tests.js +125 -112
  54. package/lib/esm/tests/tests.js.map +1 -1
  55. package/lib/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +10 -10
  57. package/src/sdk.ts +9 -1
  58. package/src/tests/api_tests/concurrent_build_threads.test.ts +103 -0
  59. package/src/tests/api_tests/custom_aggregation.test.ts +74 -0
  60. package/src/tests/api_tests/custom_dashboards.test.ts +258 -0
  61. package/src/tests/api_tests/inbox_thread_assignment_updates.test.ts +431 -1
  62. package/src/tests/api_tests/no_access_permission_checks.test.ts +365 -0
  63. package/src/tests/tests.ts +8 -1
  64. package/test_generated.pdf +0 -0
@@ -0,0 +1,103 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ assert,
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 concurrent_build_threads_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
13
+ log_header("Concurrent Build Threads Test")
14
+
15
+ const timestamp = Date.now()
16
+ const from = new Date(Date.now() - 1000 * 60 * 60) // 1 hour ago
17
+
18
+ // Create a test enduser with a phone number
19
+ const testEnduser = await sdk.api.endusers.createOne({
20
+ fname: "ConcurrentTest",
21
+ lname: "Patient",
22
+ email: `concurrent-test-${timestamp}@test.com`,
23
+ phone: "+15555550101",
24
+ })
25
+
26
+ const phoneNumber = "+15555550102"
27
+ const enduserPhoneNumber = "+15555550101"
28
+
29
+ // Create a test SMS message for the enduser
30
+ const sms = await sdk.api.sms_messages.createOne({
31
+ enduserId: testEnduser.id,
32
+ phoneNumber,
33
+ enduserPhoneNumber,
34
+ message: 'Test message for concurrent build test',
35
+ inbound: true,
36
+ logOnly: true,
37
+ })
38
+
39
+ try {
40
+ // Reset threads first to ensure clean state
41
+ await sdk.api.inbox_threads.reset_threads()
42
+
43
+ // Fire 3 concurrent load_threads requests with autobuild: true
44
+ console.log("Firing 3 concurrent load_threads requests with autobuild: true...")
45
+ const results = await Promise.all([
46
+ sdk.api.inbox_threads.load_threads({ autobuild: true }),
47
+ sdk.api.inbox_threads.load_threads({ autobuild: true }),
48
+ sdk.api.inbox_threads.load_threads({ autobuild: true }),
49
+ ])
50
+
51
+ // Also fetch all threads to see if duplicates were created
52
+ const allThreadsResult = await sdk.api.inbox_threads.load_threads({})
53
+ const allThreads = allThreadsResult.threads
54
+
55
+ // Filter for threads belonging to our test enduser
56
+ const enduserThreads = allThreads.filter(t =>
57
+ t.enduserIds?.includes(testEnduser.id) && t.type === 'SMS'
58
+ )
59
+
60
+ console.log(`Found ${enduserThreads.length} SMS threads for test enduser`)
61
+
62
+ // Assert: should only have 1 thread, not duplicates
63
+ assert(
64
+ enduserThreads.length === 1,
65
+ `Expected 1 SMS thread for enduser, found ${enduserThreads.length}. ` +
66
+ `Thread IDs: ${enduserThreads.map(t => t.id).join(', ')}`
67
+ )
68
+
69
+ console.log("✅ Concurrent build test passed - no duplicate threads created")
70
+ } finally {
71
+ // Cleanup
72
+ console.log("Cleaning up test data...")
73
+ await sdk.api.sms_messages.deleteOne(sms.id)
74
+ await sdk.api.endusers.deleteOne(testEnduser.id)
75
+ // Clean up any threads created for this enduser
76
+ const cleanupThreads = await sdk.api.inbox_threads.load_threads({})
77
+ for (const thread of cleanupThreads.threads) {
78
+ if (thread.enduserIds?.includes(testEnduser.id)) {
79
+ await sdk.api.inbox_threads.deleteOne(thread.id)
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ if (require.main === module) {
86
+ const sdk = new Session({ host })
87
+ const sdkNonAdmin = new Session({ host })
88
+
89
+ const runTests = async () => {
90
+ await setup_tests(sdk, sdkNonAdmin)
91
+ await concurrent_build_threads_tests({ sdk, sdkNonAdmin })
92
+ }
93
+
94
+ runTests()
95
+ .then(() => {
96
+ console.log("✅ Concurrent build threads test completed")
97
+ process.exit(0)
98
+ })
99
+ .catch((error) => {
100
+ console.error("❌ Concurrent build threads test failed:", error)
101
+ process.exit(1)
102
+ })
103
+ }
@@ -4,10 +4,13 @@ import { Session } from "../../sdk"
4
4
  import {
5
5
  async_test,
6
6
  log_header,
7
+ wait,
7
8
  } from "@tellescope/testing"
8
9
  import { setup_tests } from "../setup"
10
+ import { PROVIDER_PERMISSIONS } from "@tellescope/constants"
9
11
 
10
12
  const host = process.env.API_URL || 'http://localhost:8080' as const
13
+ const [nonAdminEmail, nonAdminPassword] = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD]
11
14
 
12
15
  // Main test function that can be called independently
13
16
  export const custom_aggregation_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
@@ -110,6 +113,77 @@ export const custom_aggregation_tests = async ({ sdk, sdkNonAdmin } : { sdk: Ses
110
113
  }}
111
114
  )
112
115
 
116
+ // ===== Role-Based Access Permission Tests =====
117
+ log_header("Custom Aggregation - Role-Based Access Tests")
118
+
119
+ // Create a role with No Access for engagement_events but full access for endusers
120
+ const noEngagementAccessRole = 'no-engagement-access-test'
121
+ const rbap = await sdk.api.role_based_access_permissions.createOne({
122
+ role: noEngagementAccessRole,
123
+ permissions: {
124
+ ...PROVIDER_PERMISSIONS,
125
+ // Override endusers to have full read access so we can test aggregation
126
+ endusers: {
127
+ create: 'All',
128
+ read: 'All',
129
+ update: 'All',
130
+ delete: 'All',
131
+ },
132
+ engagement_events: {
133
+ create: null,
134
+ read: null,
135
+ update: null,
136
+ delete: null,
137
+ },
138
+ },
139
+ })
140
+
141
+ // Save original role to restore later
142
+ const originalRoles = sdkNonAdmin.userInfo.roles
143
+
144
+ try {
145
+ // Assign the restricted role to non-admin user
146
+ await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: [noEngagementAccessRole] }, { replaceObjectFields: true })
147
+ await wait(undefined, 1500) // wait for role change to propagate
148
+ await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!)
149
+
150
+ // Test 6: Non-admin can still aggregate models they have access to (endusers)
151
+ await async_test(
152
+ "Custom aggregation - non-admin can access permitted models",
153
+ () => sdkNonAdmin.api.analytics_frames.custom_aggregation({
154
+ modelName: 'endusers',
155
+ aggregation: [
156
+ { $match: { fname: 'CustomAgg' } },
157
+ { $count: 'total' }
158
+ ]
159
+ }),
160
+ { onResult: r => r.result[0]?.total === 1 }
161
+ )
162
+
163
+ // Test 7: Non-admin is blocked from aggregating models with No Access
164
+ await async_test(
165
+ "Custom aggregation - non-admin blocked from No Access models",
166
+ () => sdkNonAdmin.api.analytics_frames.custom_aggregation({
167
+ modelName: 'engagement_events',
168
+ aggregation: [
169
+ { $match: {} },
170
+ { $count: 'total' }
171
+ ]
172
+ }),
173
+ { shouldError: true, onError: (e: any) => e.message === "You do not have access to this resource" }
174
+ )
175
+
176
+ console.log("✅ All custom aggregation role-based access tests passed")
177
+ } finally {
178
+ // Restore original role
179
+ await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: originalRoles }, { replaceObjectFields: true })
180
+ await wait(undefined, 1000)
181
+ await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!)
182
+
183
+ // Cleanup role
184
+ await sdk.api.role_based_access_permissions.deleteOne(rbap.id)
185
+ }
186
+
113
187
  console.log("✅ All custom aggregation tests passed")
114
188
  } finally {
115
189
  // Cleanup
@@ -0,0 +1,258 @@
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 custom_dashboards_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
13
+ log_header("Custom Dashboards")
14
+
15
+ // Test 1: Create basic dashboard with minimal fields
16
+ const basicDashboard = await sdk.api.custom_dashboards.createOne({
17
+ title: "Test Dashboard",
18
+ blocks: [{ type: "Inbox", colSpan: 4 }],
19
+ })
20
+
21
+ await async_test(
22
+ "Basic dashboard created correctly",
23
+ async () => basicDashboard,
24
+ {
25
+ onResult: r => (
26
+ r.title === "Test Dashboard"
27
+ && r.blocks.length === 1
28
+ && r.blocks[0].type === "Inbox"
29
+ && r.blocks[0].colSpan === 4
30
+ )
31
+ }
32
+ )
33
+
34
+ // Test 1b: Duplicate titles are allowed
35
+ const duplicateTitleDashboard = await sdk.api.custom_dashboards.createOne({
36
+ title: "Test Dashboard",
37
+ blocks: [{ type: "Tickets", colSpan: 6 }],
38
+ })
39
+
40
+ await async_test(
41
+ "Duplicate title dashboard created successfully",
42
+ async () => duplicateTitleDashboard,
43
+ {
44
+ onResult: r => (
45
+ r.title === "Test Dashboard"
46
+ && r.id !== basicDashboard.id
47
+ )
48
+ }
49
+ )
50
+
51
+ // Test 2: Create dashboard with all fields
52
+ const fullDashboard = await sdk.api.custom_dashboards.createOne({
53
+ title: "Full Dashboard",
54
+ description: "A complete dashboard with all options",
55
+ blocks: [
56
+ { type: "Inbox", info: { showUnread: true }, colSpan: 4, rowSpan: 2 },
57
+ { type: "Tickets", info: { status: "open" }, colSpan: 4, rowSpan: 1 },
58
+ {
59
+ type: "CustomWidget",
60
+ info: { widgetId: "xyz", nestedConfig: { foo: "bar" } },
61
+ colSpan: 4,
62
+ responsive: {
63
+ sm: { colSpan: 12, hidden: false },
64
+ md: { colSpan: 6 },
65
+ lg: { colSpan: 4 }
66
+ },
67
+ style: { backgroundColor: "#ffffff", borderRadius: 8 }
68
+ },
69
+ ],
70
+ defaultForRoles: ["Admin", "Manager"],
71
+ hiddenFromRoles: ["Guest"],
72
+ defaultForUserIds: [],
73
+ gridConfig: { columns: 12, gap: 16, rowHeight: 100 },
74
+ })
75
+
76
+ await async_test(
77
+ "Full dashboard with all fields created correctly",
78
+ async () => fullDashboard,
79
+ {
80
+ onResult: r => (
81
+ r.blocks.length === 3
82
+ && r.gridConfig !== undefined && r.gridConfig.columns === 12
83
+ && r.gridConfig !== undefined && r.gridConfig.gap === 16
84
+ && r.defaultForRoles !== undefined && r.defaultForRoles.includes("Admin")
85
+ && r.blocks[2].responsive !== undefined && r.blocks[2].responsive.sm !== undefined && r.blocks[2].responsive.sm.colSpan === 12
86
+ && r.blocks[0].info !== undefined && r.blocks[0].info.showUnread === true
87
+ )
88
+ }
89
+ )
90
+
91
+ // Test 2b: Create dashboard with userIds
92
+ const dashboardWithUserIds = await sdk.api.custom_dashboards.createOne({
93
+ title: "Dashboard With UserIds",
94
+ blocks: [{ type: "Inbox", colSpan: 12 }],
95
+ userIds: [sdk.userInfo.id],
96
+ })
97
+
98
+ await async_test(
99
+ "Dashboard with userIds created correctly",
100
+ async () => dashboardWithUserIds,
101
+ {
102
+ onResult: r => (
103
+ r.userIds !== undefined
104
+ && r.userIds.length === 1
105
+ && r.userIds[0] === sdk.userInfo.id
106
+ )
107
+ }
108
+ )
109
+
110
+ // Test 3: Update dashboard title only (blocks array updates append by default)
111
+ const updatedTitle = await sdk.api.custom_dashboards.updateOne(basicDashboard.id, {
112
+ title: "Updated Dashboard",
113
+ })
114
+
115
+ await async_test(
116
+ "Dashboard title updated correctly",
117
+ async () => updatedTitle,
118
+ {
119
+ onResult: r => (
120
+ r.title === "Updated Dashboard"
121
+ && r.blocks.length === 1
122
+ && r.blocks[0].type === "Inbox"
123
+ )
124
+ }
125
+ )
126
+
127
+ // Test 3b: Create a new dashboard to test blocks with new block types
128
+ const dashboardWithNewTypes = await sdk.api.custom_dashboards.createOne({
129
+ title: "Dashboard With Custom Types",
130
+ blocks: [
131
+ { type: "Inbox", colSpan: 6 },
132
+ { type: "NewBlockType", info: { custom: "data", nested: { value: 123 } }, colSpan: 6 },
133
+ ],
134
+ })
135
+
136
+ await async_test(
137
+ "Dashboard with new block types created correctly",
138
+ async () => dashboardWithNewTypes,
139
+ {
140
+ onResult: r => (
141
+ r.blocks.length === 2
142
+ && r.blocks[1].type === "NewBlockType"
143
+ && r.blocks[1].info !== undefined && r.blocks[1].info.custom === "data"
144
+ )
145
+ }
146
+ )
147
+
148
+ // Test 4: Read dashboard by ID
149
+ const read = await sdk.api.custom_dashboards.getOne(dashboardWithNewTypes.id)
150
+
151
+ await async_test(
152
+ "Dashboard read by ID correctly",
153
+ async () => read,
154
+ {
155
+ onResult: r => (
156
+ r.id === dashboardWithNewTypes.id
157
+ && r.title === "Dashboard With Custom Types"
158
+ )
159
+ }
160
+ )
161
+
162
+ // Test 5: List dashboards
163
+ const list = await sdk.api.custom_dashboards.getSome({ filter: {} })
164
+
165
+ await async_test(
166
+ "Dashboard list returned",
167
+ async () => list,
168
+ { onResult: r => r.length >= 2 }
169
+ )
170
+
171
+ // Test 6: Create dashboard with complex/arbitrary block info to verify permissive validation
172
+ const dashboardWithComplexBlocks = await sdk.api.custom_dashboards.createOne({
173
+ title: "Complex Blocks Dashboard",
174
+ blocks: [
175
+ {
176
+ type: "ChartWidget",
177
+ info: {
178
+ chartType: "bar",
179
+ dataSource: "appointments",
180
+ dateRange: "30d",
181
+ colors: ["#ff0000", "#00ff00"],
182
+ aggregation: { field: "status", method: "count" }
183
+ },
184
+ colSpan: 8,
185
+ rowSpan: 2,
186
+ },
187
+ ],
188
+ })
189
+
190
+ await async_test(
191
+ "Dashboard with complex block info created correctly",
192
+ async () => dashboardWithComplexBlocks,
193
+ {
194
+ onResult: r => (
195
+ r.blocks[0].type === "ChartWidget"
196
+ && r.blocks[0].info !== undefined && r.blocks[0].info.chartType === "bar"
197
+ && r.blocks[0].info !== undefined && Array.isArray(r.blocks[0].info.colors) && r.blocks[0].info.colors.length === 2
198
+ )
199
+ }
200
+ )
201
+
202
+ // Test 7: Update gridConfig only
203
+ const updatedGridConfig = await sdk.api.custom_dashboards.updateOne(dashboardWithNewTypes.id, {
204
+ gridConfig: { columns: 24, gap: 8 },
205
+ })
206
+
207
+ await async_test(
208
+ "GridConfig updated correctly",
209
+ async () => updatedGridConfig,
210
+ {
211
+ onResult: r => (
212
+ r.gridConfig !== undefined && r.gridConfig.columns === 24
213
+ && r.gridConfig !== undefined && r.gridConfig.gap === 8
214
+ )
215
+ }
216
+ )
217
+
218
+ // Test 8: Non-admin can read all dashboards by default
219
+ const nonAdminList = await sdkNonAdmin.api.custom_dashboards.getSome({ filter: {} })
220
+
221
+ await async_test(
222
+ "Non-admin can read all dashboards",
223
+ async () => nonAdminList,
224
+ { onResult: r => r.length >= 2 }
225
+ )
226
+
227
+ // Cleanup
228
+ await Promise.all([
229
+ sdk.api.custom_dashboards.deleteOne(basicDashboard.id),
230
+ sdk.api.custom_dashboards.deleteOne(duplicateTitleDashboard.id),
231
+ sdk.api.custom_dashboards.deleteOne(fullDashboard.id),
232
+ sdk.api.custom_dashboards.deleteOne(dashboardWithUserIds.id),
233
+ sdk.api.custom_dashboards.deleteOne(dashboardWithNewTypes.id),
234
+ sdk.api.custom_dashboards.deleteOne(dashboardWithComplexBlocks.id),
235
+ ])
236
+ }
237
+
238
+ // Allow running this test file independently
239
+ if (require.main === module) {
240
+ console.log(`🌐 Using API URL: ${host}`)
241
+ const sdk = new Session({ host })
242
+ const sdkNonAdmin = new Session({ host })
243
+
244
+ const runTests = async () => {
245
+ await setup_tests(sdk, sdkNonAdmin)
246
+ await custom_dashboards_tests({ sdk, sdkNonAdmin })
247
+ }
248
+
249
+ runTests()
250
+ .then(() => {
251
+ console.log("✅ Custom dashboards test suite completed successfully")
252
+ process.exit(0)
253
+ })
254
+ .catch((error) => {
255
+ console.error("❌ Custom dashboards test suite failed:", error)
256
+ process.exit(1)
257
+ })
258
+ }