@tellescope/sdk 1.253.1 → 1.254.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/sdk.d.ts +4 -0
  2. package/lib/cjs/sdk.d.ts.map +1 -1
  3. package/lib/cjs/tests/api_tests/_tmp_bc_verify.test.d.ts.map +1 -0
  4. package/lib/cjs/tests/api_tests/_tmp_blank_status_verify.test.d.ts.map +1 -0
  5. package/lib/cjs/tests/api_tests/_tmp_followup_verify.test.d.ts.map +1 -0
  6. package/lib/cjs/tests/api_tests/_tmp_nonmatch_verify.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/field_redaction.test.d.ts.map +1 -1
  8. package/lib/cjs/tests/api_tests/field_redaction.test.js +112 -99
  9. package/lib/cjs/tests/api_tests/field_redaction.test.js.map +1 -1
  10. package/lib/cjs/tests/api_tests/journey_error_branching.test.js +7 -2
  11. package/lib/cjs/tests/api_tests/journey_error_branching.test.js.map +1 -1
  12. package/lib/cjs/tests/api_tests/resource_access_tags.test.d.ts +22 -0
  13. package/lib/cjs/tests/api_tests/resource_access_tags.test.d.ts.map +1 -0
  14. package/lib/cjs/tests/api_tests/resource_access_tags.test.js +488 -0
  15. package/lib/cjs/tests/api_tests/resource_access_tags.test.js.map +1 -0
  16. package/lib/cjs/tests/api_tests/time_tracks.test.d.ts +8 -0
  17. package/lib/cjs/tests/api_tests/time_tracks.test.d.ts.map +1 -1
  18. package/lib/cjs/tests/api_tests/time_tracks.test.js +638 -1
  19. package/lib/cjs/tests/api_tests/time_tracks.test.js.map +1 -1
  20. package/lib/cjs/tests/tests.d.ts.map +1 -1
  21. package/lib/cjs/tests/tests.js +586 -178
  22. package/lib/cjs/tests/tests.js.map +1 -1
  23. package/lib/esm/sdk.d.ts +4 -0
  24. package/lib/esm/sdk.d.ts.map +1 -1
  25. package/lib/esm/tests/api_tests/_tmp_bc_verify.test.d.ts +2 -0
  26. package/lib/esm/tests/api_tests/_tmp_bc_verify.test.d.ts.map +1 -0
  27. package/lib/esm/tests/api_tests/_tmp_bc_verify.test.js +120 -0
  28. package/lib/esm/tests/api_tests/_tmp_bc_verify.test.js.map +1 -0
  29. package/lib/esm/tests/api_tests/_tmp_blank_status_verify.test.d.ts +2 -0
  30. package/lib/esm/tests/api_tests/_tmp_blank_status_verify.test.d.ts.map +1 -0
  31. package/lib/esm/tests/api_tests/_tmp_blank_status_verify.test.js +200 -0
  32. package/lib/esm/tests/api_tests/_tmp_blank_status_verify.test.js.map +1 -0
  33. package/lib/esm/tests/api_tests/_tmp_followup_verify.test.d.ts +2 -0
  34. package/lib/esm/tests/api_tests/_tmp_followup_verify.test.d.ts.map +1 -0
  35. package/lib/esm/tests/api_tests/_tmp_followup_verify.test.js +194 -0
  36. package/lib/esm/tests/api_tests/_tmp_followup_verify.test.js.map +1 -0
  37. package/lib/esm/tests/api_tests/_tmp_nonmatch_verify.test.d.ts +2 -0
  38. package/lib/esm/tests/api_tests/_tmp_nonmatch_verify.test.d.ts.map +1 -0
  39. package/lib/esm/tests/api_tests/_tmp_nonmatch_verify.test.js +115 -0
  40. package/lib/esm/tests/api_tests/_tmp_nonmatch_verify.test.js.map +1 -0
  41. package/lib/esm/tests/api_tests/field_redaction.test.d.ts.map +1 -1
  42. package/lib/esm/tests/api_tests/field_redaction.test.js +112 -99
  43. package/lib/esm/tests/api_tests/field_redaction.test.js.map +1 -1
  44. package/lib/esm/tests/api_tests/journey_error_branching.test.js +7 -2
  45. package/lib/esm/tests/api_tests/journey_error_branching.test.js.map +1 -1
  46. package/lib/esm/tests/api_tests/resource_access_tags.test.d.ts +22 -0
  47. package/lib/esm/tests/api_tests/resource_access_tags.test.d.ts.map +1 -0
  48. package/lib/esm/tests/api_tests/resource_access_tags.test.js +484 -0
  49. package/lib/esm/tests/api_tests/resource_access_tags.test.js.map +1 -0
  50. package/lib/esm/tests/api_tests/time_tracks.test.d.ts +8 -0
  51. package/lib/esm/tests/api_tests/time_tracks.test.d.ts.map +1 -1
  52. package/lib/esm/tests/api_tests/time_tracks.test.js +635 -0
  53. package/lib/esm/tests/api_tests/time_tracks.test.js.map +1 -1
  54. package/lib/esm/tests/tests.d.ts.map +1 -1
  55. package/lib/esm/tests/tests.js +587 -179
  56. package/lib/esm/tests/tests.js.map +1 -1
  57. package/lib/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +10 -10
  59. package/src/tests/api_tests/field_redaction.test.ts +13 -0
  60. package/src/tests/api_tests/journey_error_branching.test.ts +5 -1
  61. package/src/tests/api_tests/resource_access_tags.test.ts +303 -0
  62. package/src/tests/api_tests/time_tracks.test.ts +420 -0
  63. package/src/tests/tests.ts +298 -5
  64. package/test_generated.pdf +0 -0
@@ -0,0 +1,303 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ handleAnyError,
7
+ log_header,
8
+ wait,
9
+ assert,
10
+ } from "@tellescope/testing"
11
+ import { PROVIDER_PERMISSIONS } from "@tellescope/constants"
12
+ import { setup_tests } from "../setup"
13
+
14
+ const host = process.env.API_URL || 'http://localhost:8080' as const
15
+
16
+ const RAND = () => Math.random().toString(36).slice(2, 10)
17
+
18
+ const TAG_A = 'resource-access-tag-A'
19
+ const TAG_B = 'resource-access-tag-B'
20
+
21
+ /**
22
+ * Tests for settings.users.enableResourceAccessTags (the `erat` session flag).
23
+ *
24
+ * When the org setting is enabled, org-level configuration resources carrying `accessTags`
25
+ * become visible only to non-admin (Default/Assigned) users whose own tags intersect the
26
+ * record's accessTags. This mirrors the existing enduser accessTags behavior, generalized
27
+ * to the 9 RESOURCE_ACCESS_TAG_MODELS.
28
+ *
29
+ * The model is ADDITIVE: a record only becomes tag-gated once an admin sets accessTags on it.
30
+ * Records without accessTags keep their existing rules (Default == creator-only). Admins and
31
+ * All-access users are unaffected.
32
+ *
33
+ * Per the throwaway-user testing guidance, this test creates throwaway users rather than
34
+ * mutating existing user roles. Tokens are minted via generate_auth_token AFTER the org
35
+ * setting is enabled, so the `erat` flag lands in their sessions.
36
+ */
37
+ export const resource_access_tags_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
38
+ log_header("Resource Access Tags Tests")
39
+
40
+ const DEFAULT_RW = { create: 'Default', read: 'Default', update: 'Default', delete: 'Default' } as const
41
+ const ALL_RW = { create: 'All', read: 'All', update: 'All', delete: 'All' } as const
42
+
43
+ const RESOURCE_MODELS = [
44
+ 'templates', 'forms', 'journeys', 'automation_triggers', 'calendar_event_templates',
45
+ 'appointment_booking_pages', 'table_views', 'files', 'managed_content_records',
46
+ ] as const
47
+
48
+ // track everything we create for cleanup
49
+ const createdUserIds: string[] = []
50
+ const createdRoleIds: string[] = []
51
+ const createdByModel: Record<string, string[]> = {}
52
+ for (const m of RESOURCE_MODELS) createdByModel[m] = []
53
+ let helperTemplateId: string | undefined // calendar_event_template used as a booking-page dependency
54
+
55
+ const orgId = sdk.userInfo.businessId
56
+
57
+ try {
58
+ // ── 1. Enable the org setting ───────────────────────────────────────────────
59
+ // Explicitly disable enduser access tags (eat) so that the tag-edit-restriction
60
+ // assertions below prove the `erat` flag alone enforces the protection (otherwise
61
+ // a residual `eat` could mask the gap). Settings deep-merge, so this is targeted.
62
+ await sdk.api.organizations.updateOne(orgId, {
63
+ settings: { users: { enableResourceAccessTags: true }, endusers: { enableAccessTags: false } },
64
+ })
65
+
66
+ // ── 2. Roles: Default read for a tag-gated user, All read for a control user ──
67
+ const defaultPermissions: any = { ...PROVIDER_PERMISSIONS }
68
+ const allPermissions: any = { ...PROVIDER_PERMISSIONS }
69
+ for (const m of RESOURCE_MODELS) {
70
+ defaultPermissions[m] = { ...DEFAULT_RW }
71
+ allPermissions[m] = { ...ALL_RW }
72
+ }
73
+
74
+ const defaultRole = `resource-access-tags-default-${RAND()}`
75
+ const allRole = `resource-access-tags-all-${RAND()}`
76
+ const rbapDefault = await sdk.api.role_based_access_permissions.createOne({ role: defaultRole, permissions: defaultPermissions })
77
+ const rbapAll = await sdk.api.role_based_access_permissions.createOne({ role: allRole, permissions: allPermissions })
78
+ createdRoleIds.push(rbapDefault.id, rbapAll.id)
79
+
80
+ // ── 3. Throwaway users + tokens (minted AFTER enabling the setting → erat set) ─
81
+ const mkUser = async (tags: string[], roles: string[]) => {
82
+ const user = await sdk.api.users.createOne({
83
+ email: `resource-access-tags-${RAND()}@tellescope.example`,
84
+ fname: 'Resource', lname: 'AccessTags',
85
+ notificationEmailsDisabled: true, verifiedEmail: true,
86
+ tags, roles,
87
+ } as any)
88
+ createdUserIds.push(user.id)
89
+ // mint AFTER the setting is enabled so the erat flag lands in the session/JWT
90
+ const { authToken, user: sessionUser } = await sdk.api.users.generate_auth_token({ id: user.id })
91
+ const session = new Session({ host })
92
+ session.setAuthToken(authToken)
93
+ session.setUserInfo(sessionUser as any)
94
+ return { session, sessionUser: sessionUser as any }
95
+ }
96
+
97
+ const { session: sdkWithTag, sessionUser: withTagUser } = await mkUser([TAG_A], [defaultRole]) // Default access, carries the gating tag
98
+ const { session: sdkWithoutTag } = await mkUser([TAG_B], [defaultRole]) // Default access, lacks the gating tag
99
+ const { session: sdkAllAccess } = await mkUser([TAG_B], [allRole]) // All access, lacks the tag (should still see everything)
100
+
101
+ // sanity check: erat made it into the gated sessions
102
+ assert(!!withTagUser.erat, 'erat flag missing on tagged user session', 'erat flag present on tagged user session')
103
+
104
+ // ── 4. Per-model record creation (tagged with TAG_A + untagged control) ──────
105
+ // calendar_event_template dependency for booking pages
106
+ const helperTemplate = await sdk.api.calendar_event_templates.createOne({ title: `RAT helper ${RAND()}`, durationInMinutes: 30 })
107
+ helperTemplateId = helperTemplate.id
108
+ createdByModel['calendar_event_templates'].push(helperTemplate.id)
109
+
110
+ const createRecord = async (model: typeof RESOURCE_MODELS[number], accessTags?: string[]): Promise<string> => {
111
+ const tags = accessTags ? { accessTags } : {}
112
+ let id: string
113
+ switch (model) {
114
+ case 'templates': {
115
+ const r = await sdk.api.templates.createOne({ title: `RAT tmpl ${RAND()}`, subject: 's', html: '<p>x</p>', message: 'm', ...tags } as any)
116
+ id = r.id; break
117
+ }
118
+ case 'forms': {
119
+ const r = await sdk.api.forms.createOne({ title: `RAT form ${RAND()}`, ...tags } as any)
120
+ id = r.id; break
121
+ }
122
+ case 'journeys': {
123
+ const r = await sdk.api.journeys.createOne({ title: `RAT jrn ${RAND()}`, ...tags } as any)
124
+ id = r.id; break
125
+ }
126
+ case 'automation_triggers': {
127
+ const r = await sdk.api.automation_triggers.createOne({
128
+ title: `RAT trg ${RAND()}`, status: 'Active',
129
+ event: { type: 'Appointment Completed', info: {} },
130
+ action: { type: 'Add Tags', info: { tags: ['x'] } },
131
+ ...tags,
132
+ } as any)
133
+ id = r.id; break
134
+ }
135
+ case 'calendar_event_templates': {
136
+ const r = await sdk.api.calendar_event_templates.createOne({ title: `RAT cet ${RAND()}`, durationInMinutes: 30, ...tags } as any)
137
+ id = r.id; break
138
+ }
139
+ case 'appointment_booking_pages': {
140
+ const r = await sdk.api.appointment_booking_pages.createOne({
141
+ title: `RAT bp ${RAND()}`, calendarEventTemplateIds: [helperTemplateId!], locationIds: [], ...tags,
142
+ } as any)
143
+ id = r.id; break
144
+ }
145
+ case 'table_views': {
146
+ const r = await sdk.api.table_views.createOne({ title: `RAT view ${RAND()}`, page: 'Ticket', columns: [], ...tags } as any)
147
+ id = r.id; break
148
+ }
149
+ case 'files': {
150
+ const { file } = await sdk.api.files.prepare_file_upload({ name: `RAT file ${RAND()}`, type: 'text/plain', size: 1 })
151
+ if (accessTags) await sdk.api.files.updateOne(file.id, { accessTags } as any)
152
+ id = file.id; break
153
+ }
154
+ case 'managed_content_records': {
155
+ const r = await sdk.api.managed_content_records.createOne({ title: `RAT content ${RAND()}`, textContent: 't', ...tags } as any)
156
+ id = r.id; break
157
+ }
158
+ default: throw new Error(`unhandled model ${model}`)
159
+ }
160
+ createdByModel[model].push(id)
161
+ return id
162
+ }
163
+
164
+ const recordsByModel: Record<string, { taggedId: string, untaggedId: string }> = {}
165
+ for (const model of RESOURCE_MODELS) {
166
+ const taggedId = await createRecord(model, [TAG_A])
167
+ const untaggedId = await createRecord(model)
168
+ recordsByModel[model] = { taggedId, untaggedId }
169
+ }
170
+
171
+ // ── 5. Assertions per model ──────────────────────────────────────────────────
172
+ const visibleIds = async (session: Session, model: string, ids: string[]): Promise<Set<string>> => {
173
+ const records = await (session.api as any)[model].getSome({ ids, limit: 100 })
174
+ return new Set(records.map((r: any) => r.id))
175
+ }
176
+
177
+ for (const model of RESOURCE_MODELS) {
178
+ const { taggedId, untaggedId } = recordsByModel[model]
179
+ const ids = [taggedId, untaggedId]
180
+
181
+ const withTag = await visibleIds(sdkWithTag, model, ids)
182
+ const withoutTag = await visibleIds(sdkWithoutTag, model, ids)
183
+ const admin = await visibleIds(sdk, model, ids)
184
+ const allAccess = await visibleIds(sdkAllAccess, model, ids)
185
+
186
+ await async_test(
187
+ `${model}: user WITH matching tag CAN read the tagged record`,
188
+ async () => withTag.has(taggedId),
189
+ { onResult: r => r === true },
190
+ )
191
+ await async_test(
192
+ `${model}: user WITHOUT matching tag CANNOT read the tagged record`,
193
+ async () => withoutTag.has(taggedId),
194
+ { onResult: r => r === false },
195
+ )
196
+ await async_test(
197
+ `${model}: untagged admin record NOT visible to tagged user (additive model)`,
198
+ async () => withTag.has(untaggedId),
199
+ { onResult: r => r === false },
200
+ )
201
+ await async_test(
202
+ `${model}: untagged admin record NOT visible to untagged user (additive model)`,
203
+ async () => withoutTag.has(untaggedId),
204
+ { onResult: r => r === false },
205
+ )
206
+ await async_test(
207
+ `${model}: admin sees both records (gating does not apply)`,
208
+ async () => admin.has(taggedId) && admin.has(untaggedId),
209
+ { onResult: r => r === true },
210
+ )
211
+ await async_test(
212
+ `${model}: All-access user sees both records (gating does not apply)`,
213
+ async () => allAccess.has(taggedId) && allAccess.has(untaggedId),
214
+ { onResult: r => r === true },
215
+ )
216
+ }
217
+
218
+ // ── 5b. Tag-edit restriction: a non-admin cannot edit tags to self-escalate ──
219
+ // With erat ON and eat OFF, the users.tags edit guard must still fire — proving
220
+ // resource access tags (not just enduser access tags) trigger the protection.
221
+ log_header("Resource Access Tags: tag-edit blocked")
222
+ const withTagUserId = createdUserIds[0]
223
+ await async_test(
224
+ `non-admin can't update own tags (erat enabled)`,
225
+ () => sdkWithTag.api.users.updateOne(withTagUserId, { tags: ['escalate'] }),
226
+ handleAnyError,
227
+ )
228
+ await async_test(
229
+ `non-admin can't update own tags alongside other fields (erat enabled)`,
230
+ () => sdkWithTag.api.users.updateOne(withTagUserId, { tags: ['escalate'], bio: '' }),
231
+ handleAnyError,
232
+ )
233
+ await async_test(
234
+ `non-admin can still update non-tag fields (control)`,
235
+ () => sdkWithTag.api.users.updateOne(withTagUserId, { bio: '' }),
236
+ { shouldError: false, onResult: () => true },
237
+ )
238
+
239
+ // ── 6. Org-setting-OFF control: disabling the flag gates the whole feature ────
240
+ log_header("Resource Access Tags: org-setting-OFF control")
241
+ await sdk.api.organizations.updateOne(orgId, {
242
+ settings: { users: { enableResourceAccessTags: false } },
243
+ })
244
+ // re-mint the tagged user's token so the (now-disabled) setting is reflected in the session
245
+ const { authToken: reAuthToken, user: reAuthUser } = await sdk.api.users.generate_auth_token({ id: createdUserIds[0] })
246
+ const sdkWithTagOff = new Session({ host })
247
+ sdkWithTagOff.setAuthToken(reAuthToken)
248
+ sdkWithTagOff.setUserInfo(reAuthUser as any)
249
+ assert(!(reAuthUser as any).erat, 'erat flag should be absent after disabling setting', 'erat flag absent after disabling setting')
250
+
251
+ const { taggedId: templatesTaggedId } = recordsByModel['templates']
252
+ const offVisible = await visibleIds(sdkWithTagOff, 'templates', [templatesTaggedId])
253
+ await async_test(
254
+ `templates: tagged record NOT visible to tagged user once setting is OFF`,
255
+ async () => offVisible.has(templatesTaggedId),
256
+ { onResult: r => r === false },
257
+ )
258
+
259
+ console.log("\n" + "=".repeat(60))
260
+ console.log("Resource Access Tags Tests Complete")
261
+ console.log("=".repeat(60))
262
+ } finally {
263
+ // ── Cleanup ────────────────────────────────────────────────────────────────
264
+ for (const model of RESOURCE_MODELS) {
265
+ for (const id of createdByModel[model]) {
266
+ await (sdk.api as any)[model].deleteOne(id).catch(() => {})
267
+ }
268
+ }
269
+ for (const id of createdRoleIds) {
270
+ await sdk.api.role_based_access_permissions.deleteOne(id).catch(() => {})
271
+ }
272
+ for (const id of createdUserIds) {
273
+ await sdk.api.users.deleteOne(id).catch(() => {})
274
+ }
275
+ // reset the org setting
276
+ await sdk.api.organizations.updateOne(orgId, {
277
+ settings: { users: { enableResourceAccessTags: false } },
278
+ }).catch(() => {})
279
+ await wait(undefined, 250)
280
+ }
281
+ }
282
+
283
+ // Allow running this test file independently
284
+ if (require.main === module) {
285
+ console.log(`🌐 Using API URL: ${host}`)
286
+ const sdk = new Session({ host })
287
+ const sdkNonAdmin = new Session({ host })
288
+
289
+ const runTests = async () => {
290
+ await setup_tests(sdk, sdkNonAdmin)
291
+ await resource_access_tags_tests({ sdk, sdkNonAdmin })
292
+ }
293
+
294
+ runTests()
295
+ .then(() => {
296
+ console.log("✅ Resource access tags test suite completed successfully")
297
+ process.exit(0)
298
+ })
299
+ .catch((error) => {
300
+ console.error("❌ Resource access tags test suite failed:", error)
301
+ process.exit(1)
302
+ })
303
+ }