@tellescope/sdk 1.256.6 → 1.256.8
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/enduser.d.ts +1 -1
- package/lib/cjs/enduser.d.ts.map +1 -1
- package/lib/cjs/enduser.js +1 -0
- package/lib/cjs/enduser.js.map +1 -1
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +505 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.js +173 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/form_started_trigger.test.js +87 -24
- package/lib/cjs/tests/api_tests/form_started_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/form_translations.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/form_translations.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/form_translations.test.js +259 -0
- package/lib/cjs/tests/api_tests/form_translations.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js +293 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js +277 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +262 -78
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +315 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js +343 -80
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js +74 -35
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js.map +1 -1
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts +134 -0
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts.map +1 -1
- package/lib/cjs/tests/stripe_webhook_helpers.js +126 -1
- package/lib/cjs/tests/stripe_webhook_helpers.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +242 -212
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +2 -1
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/enduser.js +1 -0
- package/lib/esm/enduser.js.map +1 -1
- package/lib/esm/sdk.d.ts +3 -2
- package/lib/esm/sdk.d.ts.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/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +501 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.js +169 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.js.map +1 -0
- package/lib/esm/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/form_started_trigger.test.js +87 -24
- package/lib/esm/tests/api_tests/form_started_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/form_translations.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/form_translations.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/form_translations.test.js +255 -0
- package/lib/esm/tests/api_tests/form_translations.test.js.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js +289 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js +273 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +262 -78
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +311 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js +344 -81
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
- package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/welle_partner_token.test.js +74 -35
- package/lib/esm/tests/api_tests/welle_partner_token.test.js.map +1 -1
- package/lib/esm/tests/stripe_webhook_helpers.d.ts +134 -0
- package/lib/esm/tests/stripe_webhook_helpers.d.ts.map +1 -1
- package/lib/esm/tests/stripe_webhook_helpers.js +119 -0
- package/lib/esm/tests/stripe_webhook_helpers.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +242 -212
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/enduser.ts +4 -2
- package/src/tests/api_tests/calendar_event_create_and_bulk_triggers.test.ts +293 -0
- package/src/tests/api_tests/configurations_public_read.test.ts +116 -0
- package/src/tests/api_tests/form_started_trigger.test.ts +49 -1
- package/src/tests/api_tests/form_translations.test.ts +205 -0
- package/src/tests/api_tests/group_event_attendee_self_update.test.ts +161 -0
- package/src/tests/api_tests/group_event_attendee_status_triggers.test.ts +135 -0
- package/src/tests/api_tests/mdi_trigger_refill.test.ts +184 -0
- package/src/tests/api_tests/purchase_made_trigger.test.ts +137 -0
- package/src/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.ts +204 -0
- package/src/tests/api_tests/security/public_endpoint_auth_field_lock.test.ts +16 -6
- package/src/tests/api_tests/stripe_webhook_triggers.test.ts +221 -0
- package/src/tests/api_tests/welle_partner_token.test.ts +49 -6
- package/src/tests/stripe_webhook_helpers.ts +116 -0
- package/src/tests/tests.ts +29 -12
- package/test_generated.pdf +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/sdk",
|
|
3
|
-
"version": "1.256.
|
|
3
|
+
"version": "1.256.8",
|
|
4
4
|
"description": "Code for interacting with the Tellescope API",
|
|
5
5
|
"main": "./lib/cjs/sdk.js",
|
|
6
6
|
"module": "./lib/esm/sdk.js",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/tellescope-os/tellescope#readme",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@tellescope/constants": "1.256.
|
|
36
|
-
"@tellescope/schema": "1.256.
|
|
37
|
-
"@tellescope/testing": "1.256.
|
|
38
|
-
"@tellescope/types-client": "1.256.
|
|
39
|
-
"@tellescope/types-models": "1.256.
|
|
40
|
-
"@tellescope/types-utilities": "1.256.
|
|
41
|
-
"@tellescope/utilities": "1.256.
|
|
42
|
-
"@tellescope/validation": "1.256.
|
|
35
|
+
"@tellescope/constants": "1.256.7",
|
|
36
|
+
"@tellescope/schema": "1.256.8",
|
|
37
|
+
"@tellescope/testing": "1.256.7",
|
|
38
|
+
"@tellescope/types-client": "1.256.7",
|
|
39
|
+
"@tellescope/types-models": "1.256.7",
|
|
40
|
+
"@tellescope/types-utilities": "1.256.7",
|
|
41
|
+
"@tellescope/utilities": "1.256.7",
|
|
42
|
+
"@tellescope/validation": "1.256.7",
|
|
43
43
|
"axios": "0.30.3",
|
|
44
44
|
"dotenv": "14.3.2",
|
|
45
45
|
"form-data": "4.0.4",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "194f5c52adb83f8950ad515f37a9c638b8233c90"
|
|
59
59
|
}
|
package/src/enduser.ts
CHANGED
|
@@ -59,6 +59,7 @@ type EnduserAccessibleModels = (
|
|
|
59
59
|
| 'allergy_codes'
|
|
60
60
|
| 'forms'
|
|
61
61
|
| 'enduser_eligibility_results'
|
|
62
|
+
| 'configurations' // reads reduced to publicRead: true records (form translation maps)
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
export const defaultQueries = <N extends keyof ClientModelForName>(
|
|
@@ -281,8 +282,9 @@ const loadDefaultQueries = (s: EnduserSession): { [K in EnduserAccessibleModels]
|
|
|
281
282
|
enduser_problems: defaultQueries(s, 'enduser_problems'),
|
|
282
283
|
diagnosis_codes: defaultQueries(s, 'diagnosis_codes'),
|
|
283
284
|
allergy_codes: defaultQueries(s, 'allergy_codes'),
|
|
284
|
-
forms: defaultQueries(s, 'forms'),
|
|
285
|
-
enduser_eligibility_results: defaultQueries(s, 'enduser_eligibility_results'),
|
|
285
|
+
forms: defaultQueries(s, 'forms'),
|
|
286
|
+
enduser_eligibility_results: defaultQueries(s, 'enduser_eligibility_results'),
|
|
287
|
+
configurations: defaultQueries(s, 'configurations'), // reads reduced to publicRead: true records (form translation maps)
|
|
286
288
|
})
|
|
287
289
|
|
|
288
290
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { Session } from "../../sdk"
|
|
2
|
+
import { log_header, wait, async_test } from "@tellescope/testing"
|
|
3
|
+
import { setup_tests } from "../setup"
|
|
4
|
+
|
|
5
|
+
const host = process.env.API_URL || "http://localhost:8080"
|
|
6
|
+
|
|
7
|
+
// Every trigger in this suite is filtered to this single title, so the suite can neither perturb nor
|
|
8
|
+
// be perturbed by other suites' calendar events. Assertions use includes()/!includes() rather than
|
|
9
|
+
// exact tag counts for the same reason.
|
|
10
|
+
const TITLE = 'Create Bulk Trigger Test'
|
|
11
|
+
|
|
12
|
+
const TAG = {
|
|
13
|
+
Created: 'CB: Created',
|
|
14
|
+
Completed: 'CB: Completed',
|
|
15
|
+
'No-showed': 'CB: No-showed',
|
|
16
|
+
Cancelled: 'CB: Cancelled',
|
|
17
|
+
CancelledByUser: 'CB: Cancelled by user',
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
export const calendar_event_create_and_bulk_triggers_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
21
|
+
log_header("Automation Trigger Tests (Create Path + bulk_update)")
|
|
22
|
+
|
|
23
|
+
const createdTriggerIds: string[] = []
|
|
24
|
+
const createdEnduserIds: string[] = []
|
|
25
|
+
const createdEventIds: string[] = []
|
|
26
|
+
|
|
27
|
+
const createEnduser = async () => {
|
|
28
|
+
const e = await sdk.api.endusers.createOne({})
|
|
29
|
+
createdEnduserIds.push(e.id)
|
|
30
|
+
return e
|
|
31
|
+
}
|
|
32
|
+
const createEvent = async (overrides: Record<string, any> = {}) => {
|
|
33
|
+
const event = await sdk.api.calendar_events.createOne({
|
|
34
|
+
title: TITLE,
|
|
35
|
+
durationInMinutes: 30,
|
|
36
|
+
startTimeInMS: Date.now() + Math.floor(Math.random() * 100000),
|
|
37
|
+
...overrides,
|
|
38
|
+
} as any)
|
|
39
|
+
createdEventIds.push(event.id)
|
|
40
|
+
return event
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tagsOf = async (enduserId: string) => (await sdk.api.endusers.getOne(enduserId)).tags || []
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
for (const [type, info, tag] of [
|
|
47
|
+
['Appointment Created', { titles: [TITLE] }, TAG.Created],
|
|
48
|
+
['Appointment Completed', { titles: [TITLE] }, TAG.Completed],
|
|
49
|
+
['Appointment No-Showed', { titles: [TITLE] }, TAG['No-showed']],
|
|
50
|
+
['Appointment Cancelled', { titles: [TITLE] }, TAG.Cancelled],
|
|
51
|
+
['Appointment Cancelled', { titles: [TITLE], by: 'user' }, TAG.CancelledByUser],
|
|
52
|
+
] as const) {
|
|
53
|
+
const t = await sdk.api.automation_triggers.createOne({
|
|
54
|
+
event: { type, info } as any,
|
|
55
|
+
action: { type: 'Add Tags', info: { tags: [tag] } },
|
|
56
|
+
status: 'Active',
|
|
57
|
+
title: tag,
|
|
58
|
+
})
|
|
59
|
+
createdTriggerIds.push(t.id)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================================
|
|
63
|
+
// SECTION 1: single create path, with per-attendee statuses already set at creation.
|
|
64
|
+
// Covers the completed-at-create branch of handle_new_events_trigger, which routes through
|
|
65
|
+
// enduser_ids_for_event_wide_completion. Nothing else in the test suite creates an event with
|
|
66
|
+
// attendeeStatuses / cancelledGroupAttendees preset.
|
|
67
|
+
// ============================================================
|
|
68
|
+
log_header("Create-time per-attendee statuses respected by completed-at-create")
|
|
69
|
+
|
|
70
|
+
const createNoShow = await createEnduser()
|
|
71
|
+
const createLegacyCancelled = await createEnduser()
|
|
72
|
+
const createActive = await createEnduser()
|
|
73
|
+
|
|
74
|
+
await createEvent({
|
|
75
|
+
attendees: [
|
|
76
|
+
{ type: 'enduser', id: createNoShow.id },
|
|
77
|
+
{ type: 'enduser', id: createLegacyCancelled.id },
|
|
78
|
+
{ type: 'enduser', id: createActive.id },
|
|
79
|
+
],
|
|
80
|
+
completedAt: new Date(),
|
|
81
|
+
completedBy: sdk.userInfo.id,
|
|
82
|
+
attendeeStatuses: [{ id: createNoShow.id, at: new Date(), status: 'No-showed' }],
|
|
83
|
+
// legacy shape: cancelled with no attendeeStatuses entry (what the portal writes)
|
|
84
|
+
cancelledGroupAttendees: [{ id: createLegacyCancelled.id, at: new Date() }],
|
|
85
|
+
})
|
|
86
|
+
await wait(undefined, 1000)
|
|
87
|
+
|
|
88
|
+
await async_test(
|
|
89
|
+
"completed-at-create fires Completed for the un-statused attendee",
|
|
90
|
+
() => tagsOf(createActive.id),
|
|
91
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.Completed) }
|
|
92
|
+
)
|
|
93
|
+
await async_test(
|
|
94
|
+
"completed-at-create does NOT fire Completed for a 'No-showed' attendee",
|
|
95
|
+
() => tagsOf(createNoShow.id),
|
|
96
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
|
|
97
|
+
)
|
|
98
|
+
await async_test(
|
|
99
|
+
"completed-at-create does NOT fire Completed for a cancelledGroupAttendees attendee",
|
|
100
|
+
() => tagsOf(createLegacyCancelled.id),
|
|
101
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
|
|
102
|
+
)
|
|
103
|
+
// the create path has no per-attendee status dispatch (that only runs as an update side effect),
|
|
104
|
+
// so a status set at creation fires nothing of its own — pinning current behavior
|
|
105
|
+
await async_test(
|
|
106
|
+
"a 'No-showed' status set at creation fires no No-Showed trigger",
|
|
107
|
+
() => tagsOf(createNoShow.id),
|
|
108
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG['No-showed']) }
|
|
109
|
+
)
|
|
110
|
+
// an event completed at creation uses the Completed trigger only, never Created
|
|
111
|
+
for (const [label, e] of [
|
|
112
|
+
['no-showed', createNoShow], ['legacy-cancelled', createLegacyCancelled], ['active', createActive],
|
|
113
|
+
] as const) {
|
|
114
|
+
await async_test(
|
|
115
|
+
`completed-at-create does NOT fire Created for the ${label} attendee`,
|
|
116
|
+
() => tagsOf(e.id),
|
|
117
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG.Created) }
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================
|
|
122
|
+
// SECTION 2: bulk create path (createSome with >1 event) — handle_new_events_trigger returns early
|
|
123
|
+
// for a single event, so more than one event is required to route through bulk_handle_actions_for_triggers.
|
|
124
|
+
// ============================================================
|
|
125
|
+
log_header("Bulk create path respects per-attendee statuses")
|
|
126
|
+
|
|
127
|
+
const bulkNoShow = await createEnduser()
|
|
128
|
+
const bulkActive = await createEnduser()
|
|
129
|
+
const bulkCreated = await createEnduser()
|
|
130
|
+
|
|
131
|
+
const bulkResult = await sdk.api.calendar_events.createSome([
|
|
132
|
+
{
|
|
133
|
+
title: TITLE,
|
|
134
|
+
durationInMinutes: 30,
|
|
135
|
+
startTimeInMS: Date.now(),
|
|
136
|
+
attendees: [{ type: 'enduser', id: bulkNoShow.id }, { type: 'enduser', id: bulkActive.id }],
|
|
137
|
+
completedAt: new Date(),
|
|
138
|
+
completedBy: sdk.userInfo.id,
|
|
139
|
+
attendeeStatuses: [{ id: bulkNoShow.id, at: new Date(), status: 'No-showed' }],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
title: TITLE,
|
|
143
|
+
durationInMinutes: 30,
|
|
144
|
+
startTimeInMS: Date.now(),
|
|
145
|
+
attendees: [{ type: 'enduser', id: bulkCreated.id }],
|
|
146
|
+
},
|
|
147
|
+
] as any)
|
|
148
|
+
createdEventIds.push(...bulkResult.created.map(e => e.id))
|
|
149
|
+
await wait(undefined, 1000)
|
|
150
|
+
|
|
151
|
+
// routing canary: a single created event short-circuits before the bulk handler, so this suite only
|
|
152
|
+
// covers the bulk branch as long as both events are created in one request
|
|
153
|
+
await async_test(
|
|
154
|
+
"bulk create routing canary: both events created in one request",
|
|
155
|
+
async () => bulkResult.created.length,
|
|
156
|
+
{ onResult: (count: number) => count === 2 }
|
|
157
|
+
)
|
|
158
|
+
await async_test(
|
|
159
|
+
"bulk create fires Completed for the un-statused attendee",
|
|
160
|
+
() => tagsOf(bulkActive.id),
|
|
161
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.Completed) }
|
|
162
|
+
)
|
|
163
|
+
await async_test(
|
|
164
|
+
"bulk create does NOT fire Completed for a 'No-showed' attendee",
|
|
165
|
+
() => tagsOf(bulkNoShow.id),
|
|
166
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG.Completed) }
|
|
167
|
+
)
|
|
168
|
+
await async_test(
|
|
169
|
+
"bulk create fires Created for an attendee of the non-completed event",
|
|
170
|
+
() => tagsOf(bulkCreated.id),
|
|
171
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.Created) }
|
|
172
|
+
)
|
|
173
|
+
await async_test(
|
|
174
|
+
"bulk create does NOT fire Created for attendees of the completed event",
|
|
175
|
+
() => tagsOf(bulkActive.id),
|
|
176
|
+
{ onResult: (tags: string[]) => !tags.includes(TAG.Created) }
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// ============================================================
|
|
180
|
+
// SECTION 3: Appointment Created fans out to every attendee of a group event.
|
|
181
|
+
// ============================================================
|
|
182
|
+
log_header("Appointment Created fires for all attendees of a group event")
|
|
183
|
+
|
|
184
|
+
const created1 = await createEnduser()
|
|
185
|
+
const created2 = await createEnduser()
|
|
186
|
+
const created3 = await createEnduser()
|
|
187
|
+
|
|
188
|
+
await createEvent({
|
|
189
|
+
attendees: [
|
|
190
|
+
{ type: 'enduser', id: created1.id },
|
|
191
|
+
{ type: 'enduser', id: created2.id },
|
|
192
|
+
{ type: 'enduser', id: created3.id },
|
|
193
|
+
],
|
|
194
|
+
})
|
|
195
|
+
await wait(undefined, 1000)
|
|
196
|
+
|
|
197
|
+
const groupCreatedAttendees = [created1, created2, created3]
|
|
198
|
+
for (let i = 0; i < groupCreatedAttendees.length; i++) {
|
|
199
|
+
const enduserId = groupCreatedAttendees[i].id
|
|
200
|
+
await async_test(
|
|
201
|
+
`group event fires Created for attendee #${i + 1}`,
|
|
202
|
+
() => tagsOf(enduserId),
|
|
203
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.Created) }
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ============================================================
|
|
208
|
+
// SECTION 4: bulk_update action 'no_show' fires the No-Showed trigger.
|
|
209
|
+
// ============================================================
|
|
210
|
+
log_header("bulk_update 'no_show' fires No-Showed for all attendees")
|
|
211
|
+
|
|
212
|
+
const noShowA = await createEnduser()
|
|
213
|
+
const noShowB = await createEnduser()
|
|
214
|
+
const noShowEvent = await createEvent({
|
|
215
|
+
attendees: [{ type: 'enduser', id: noShowA.id }, { type: 'enduser', id: noShowB.id }],
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
await sdk.api.calendar_events.bulk_update({ ids: [noShowEvent.id], action: 'no_show' })
|
|
219
|
+
await wait(undefined, 1000)
|
|
220
|
+
|
|
221
|
+
await async_test(
|
|
222
|
+
"bulk_update 'no_show' fires No-Showed for attendee #1",
|
|
223
|
+
() => tagsOf(noShowA.id),
|
|
224
|
+
{ onResult: (tags: string[]) => tags.includes(TAG['No-showed']) }
|
|
225
|
+
)
|
|
226
|
+
await async_test(
|
|
227
|
+
"bulk_update 'no_show' fires No-Showed for attendee #2",
|
|
228
|
+
() => tagsOf(noShowB.id),
|
|
229
|
+
{ onResult: (tags: string[]) => tags.includes(TAG['No-showed']) }
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// ============================================================
|
|
233
|
+
// SECTION 5: bulk_update action 'cancel' fires the Cancelled trigger, including by:'user'
|
|
234
|
+
// (the bulk cancel writes statusChangeSource: { source: 'user' }).
|
|
235
|
+
// ============================================================
|
|
236
|
+
log_header("bulk_update 'cancel' fires Cancelled for all attendees")
|
|
237
|
+
|
|
238
|
+
const cancelA = await createEnduser()
|
|
239
|
+
const cancelB = await createEnduser()
|
|
240
|
+
const cancelEvent = await createEvent({
|
|
241
|
+
attendees: [{ type: 'enduser', id: cancelA.id }, { type: 'enduser', id: cancelB.id }],
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
await sdk.api.calendar_events.bulk_update({ ids: [cancelEvent.id], action: 'cancel' })
|
|
245
|
+
await wait(undefined, 1000)
|
|
246
|
+
|
|
247
|
+
for (const [label, e] of [['#1', cancelA], ['#2', cancelB]] as const) {
|
|
248
|
+
await async_test(
|
|
249
|
+
`bulk_update 'cancel' fires Cancelled for attendee ${label}`,
|
|
250
|
+
() => tagsOf(e.id),
|
|
251
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.Cancelled) }
|
|
252
|
+
)
|
|
253
|
+
await async_test(
|
|
254
|
+
`bulk_update 'cancel' fires by:'user' Cancelled for attendee ${label}`,
|
|
255
|
+
() => tagsOf(e.id),
|
|
256
|
+
{ onResult: (tags: string[]) => tags.includes(TAG.CancelledByUser) }
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
} finally {
|
|
261
|
+
for (const id of createdEventIds) {
|
|
262
|
+
try { await sdk.api.calendar_events.deleteOne(id) } catch (_) {}
|
|
263
|
+
}
|
|
264
|
+
for (const id of createdEnduserIds) {
|
|
265
|
+
try { await sdk.api.endusers.deleteOne(id) } catch (_) {}
|
|
266
|
+
}
|
|
267
|
+
for (const id of createdTriggerIds) {
|
|
268
|
+
try { await sdk.api.automation_triggers.deleteOne(id) } catch (_) {}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Allow running this test file independently
|
|
274
|
+
if (require.main === module) {
|
|
275
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
276
|
+
const sdk = new Session({ host })
|
|
277
|
+
const sdkNonAdmin = new Session({ host })
|
|
278
|
+
|
|
279
|
+
const runTests = async () => {
|
|
280
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
281
|
+
await calendar_event_create_and_bulk_triggers_tests({ sdk, sdkNonAdmin })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
runTests()
|
|
285
|
+
.then(() => {
|
|
286
|
+
console.log("✅ Calendar event create path + bulk_update trigger tests completed successfully")
|
|
287
|
+
process.exit(0)
|
|
288
|
+
})
|
|
289
|
+
.catch((error) => {
|
|
290
|
+
console.error("❌ Calendar event create path + bulk_update trigger tests failed:", error)
|
|
291
|
+
process.exit(1)
|
|
292
|
+
})
|
|
293
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session, EnduserSession } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
assert,
|
|
6
|
+
async_test,
|
|
7
|
+
handleAnyError,
|
|
8
|
+
log_header,
|
|
9
|
+
} from "@tellescope/testing"
|
|
10
|
+
import { setup_tests } from "../setup"
|
|
11
|
+
|
|
12
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
13
|
+
|
|
14
|
+
// Modular test for the configurations publicRead flag (added for AI form translation maps).
|
|
15
|
+
//
|
|
16
|
+
// The critical security property: configurations hold sensitive integration settings (API keys etc.),
|
|
17
|
+
// so enduser read access is opt-in PER RECORD via publicRead: true. A configuration without the flag
|
|
18
|
+
// must remain completely invisible to enduser sessions, and endusers must never be able to write
|
|
19
|
+
// configurations at all.
|
|
20
|
+
export const configurations_public_read_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
21
|
+
log_header("Configurations publicRead Tests")
|
|
22
|
+
|
|
23
|
+
const createdConfigurationIds: string[] = []
|
|
24
|
+
let enduserId: string | undefined
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// config A simulates an integration config (no flag); config B is a flagged translation-map-style record
|
|
28
|
+
const configA = await sdk.api.configurations.createOne({
|
|
29
|
+
type: 'test-private-configuration',
|
|
30
|
+
value: JSON.stringify({ apiKey: 'super-secret' }),
|
|
31
|
+
})
|
|
32
|
+
createdConfigurationIds.push(configA.id)
|
|
33
|
+
|
|
34
|
+
const configB = await sdk.api.configurations.createOne({
|
|
35
|
+
type: 'test-public-configuration',
|
|
36
|
+
value: JSON.stringify({ Hello: 'Hola' }),
|
|
37
|
+
publicRead: true,
|
|
38
|
+
})
|
|
39
|
+
createdConfigurationIds.push(configB.id)
|
|
40
|
+
|
|
41
|
+
const enduser = await sdk.api.endusers.createOne({
|
|
42
|
+
email: `configurations_public_read_${Date.now()}@test.tellescope.com`,
|
|
43
|
+
})
|
|
44
|
+
enduserId = enduser.id
|
|
45
|
+
|
|
46
|
+
const { authToken, enduser: authEnduser } = await sdk.api.endusers.generate_auth_token({ id: enduser.id })
|
|
47
|
+
const enduserSDK = new EnduserSession({ host, authToken, enduser: authEnduser, businessId: sdk.userInfo.businessId })
|
|
48
|
+
|
|
49
|
+
// --- reads ---
|
|
50
|
+
await async_test(
|
|
51
|
+
'enduser CAN read a publicRead configuration by id',
|
|
52
|
+
() => enduserSDK.api.configurations.getOne(configB.id),
|
|
53
|
+
{ onResult: c => c?.id === configB.id && c.value === configB.value },
|
|
54
|
+
)
|
|
55
|
+
await async_test(
|
|
56
|
+
'enduser CANNOT read an unflagged configuration by id (key security assertion)',
|
|
57
|
+
() => enduserSDK.api.configurations.getOne(configA.id),
|
|
58
|
+
handleAnyError,
|
|
59
|
+
)
|
|
60
|
+
await async_test(
|
|
61
|
+
'enduser getSome returns only publicRead configurations',
|
|
62
|
+
() => enduserSDK.api.configurations.getSome({}),
|
|
63
|
+
{ onResult: configurations => (
|
|
64
|
+
configurations.some(c => c.id === configB.id)
|
|
65
|
+
&& !configurations.some(c => c.id === configA.id)
|
|
66
|
+
)},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// --- writes: endusers have no create/update/delete on configurations ---
|
|
70
|
+
await async_test(
|
|
71
|
+
'enduser CANNOT create a configuration',
|
|
72
|
+
() => enduserSDK.api.configurations.createOne({ type: 'test-enduser-create', value: '{}', publicRead: true }),
|
|
73
|
+
handleAnyError,
|
|
74
|
+
)
|
|
75
|
+
await async_test(
|
|
76
|
+
'enduser CANNOT update a configuration (even a publicRead one)',
|
|
77
|
+
() => enduserSDK.api.configurations.updateOne(configB.id, { value: '{}' }),
|
|
78
|
+
handleAnyError,
|
|
79
|
+
)
|
|
80
|
+
await async_test(
|
|
81
|
+
'enduser CANNOT delete a configuration',
|
|
82
|
+
() => enduserSDK.api.configurations.deleteOne(configB.id),
|
|
83
|
+
handleAnyError,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// staff access is unchanged by the flag
|
|
87
|
+
const staffRead = await sdk.api.configurations.getOne(configA.id)
|
|
88
|
+
assert(staffRead?.id === configA.id, 'staff could not read unflagged configuration', 'staff reads unflagged configuration')
|
|
89
|
+
} finally {
|
|
90
|
+
for (const id of createdConfigurationIds) {
|
|
91
|
+
await sdk.api.configurations.deleteOne(id).catch(() => {})
|
|
92
|
+
}
|
|
93
|
+
if (enduserId) await sdk.api.endusers.deleteOne(enduserId).catch(() => {})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Allow running this test file independently
|
|
98
|
+
if (require.main === module) {
|
|
99
|
+
const sdk = new Session({ host })
|
|
100
|
+
const sdkNonAdmin = new Session({ host })
|
|
101
|
+
|
|
102
|
+
const runTests = async () => {
|
|
103
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
104
|
+
await configurations_public_read_tests({ sdk, sdkNonAdmin })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
runTests()
|
|
108
|
+
.then(() => {
|
|
109
|
+
console.log("✅ Configurations publicRead test suite completed successfully")
|
|
110
|
+
process.exit(0)
|
|
111
|
+
})
|
|
112
|
+
.catch((error) => {
|
|
113
|
+
console.error("❌ Configurations publicRead test suite failed:", error)
|
|
114
|
+
process.exit(1)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
@@ -30,13 +30,14 @@ export const form_started_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk: S
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// Helper to start a public form session (this triggers Form Started)
|
|
33
|
-
const startPublicForm = async (email: string) => {
|
|
33
|
+
const startPublicForm = async (email: string, publicIdentifier?: string) => {
|
|
34
34
|
const enduserSDK = new EnduserSession({ host, businessId: form.businessId })
|
|
35
35
|
return await enduserSDK.api.form_responses.session_for_public_form({
|
|
36
36
|
formId: form.id,
|
|
37
37
|
businessId: form.businessId,
|
|
38
38
|
email,
|
|
39
39
|
skipMatch: true,
|
|
40
|
+
...publicIdentifier ? { publicIdentifier } : {},
|
|
40
41
|
})
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -257,11 +258,58 @@ export const form_started_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk: S
|
|
|
257
258
|
|
|
258
259
|
await sdk.api.automation_triggers.deleteOne(triggerFinalized.id)
|
|
259
260
|
|
|
261
|
+
// Test 7: {{PUBLIC_IDENTIFIER}} resolves in Add Tags / Set Fields, matching Form Submitted
|
|
262
|
+
const triggerPublicIdentifierTag = await sdk.api.automation_triggers.createOne({
|
|
263
|
+
event: { type: 'Form Started', info: { formIds: [form.id] } },
|
|
264
|
+
action: { type: 'Add Tags', info: { tags: ['{{PUBLIC_IDENTIFIER}}'] } },
|
|
265
|
+
status: 'Active',
|
|
266
|
+
title: "Public identifier tag",
|
|
267
|
+
})
|
|
268
|
+
const triggerPublicIdentifierField = await sdk.api.automation_triggers.createOne({
|
|
269
|
+
event: { type: 'Form Started', info: { formIds: [form.id] } },
|
|
270
|
+
action: { type: 'Set Fields', info: { fields: [{
|
|
271
|
+
name: 'fstSource', type: 'Custom Value', value: '{{PUBLIC_IDENTIFIER}}',
|
|
272
|
+
}] } },
|
|
273
|
+
status: 'Active',
|
|
274
|
+
title: "Public identifier field",
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const PUBLIC_IDENTIFIER = 'fst-pubid-abc123'
|
|
278
|
+
const { enduserId: enduserId11 } = await startPublicForm('fst-test11@tellescope.com', PUBLIC_IDENTIFIER)
|
|
279
|
+
await wait(undefined, 500)
|
|
280
|
+
await async_test(
|
|
281
|
+
"{{PUBLIC_IDENTIFIER}} resolves to the identifier in Add Tags",
|
|
282
|
+
() => sdk.api.endusers.getOne(enduserId11),
|
|
283
|
+
{ onResult: (e: Enduser) => (
|
|
284
|
+
!!e.tags?.includes(PUBLIC_IDENTIFIER) && !e.tags?.includes('{{PUBLIC_IDENTIFIER}}')
|
|
285
|
+
)}
|
|
286
|
+
)
|
|
287
|
+
await async_test(
|
|
288
|
+
"{{PUBLIC_IDENTIFIER}} resolves to the identifier in Set Fields",
|
|
289
|
+
() => sdk.api.endusers.getOne(enduserId11),
|
|
290
|
+
{ onResult: (e: Enduser) => e.fields?.fstSource === PUBLIC_IDENTIFIER }
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
// no publicIdentifier on the link => literal is left as-is (same guard as Form Submitted)
|
|
294
|
+
const { enduserId: enduserId12 } = await startPublicForm('fst-test12@tellescope.com')
|
|
295
|
+
await wait(undefined, 500)
|
|
296
|
+
await async_test(
|
|
297
|
+
"{{PUBLIC_IDENTIFIER}} left as literal when form started without an identifier",
|
|
298
|
+
() => sdk.api.endusers.getOne(enduserId12),
|
|
299
|
+
{ onResult: (e: Enduser) => (
|
|
300
|
+
!!e.tags?.includes('{{PUBLIC_IDENTIFIER}}') && e.fields?.fstSource === '{{PUBLIC_IDENTIFIER}}'
|
|
301
|
+
)}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
await sdk.api.automation_triggers.deleteOne(triggerPublicIdentifierTag.id)
|
|
305
|
+
await sdk.api.automation_triggers.deleteOne(triggerPublicIdentifierField.id)
|
|
306
|
+
|
|
260
307
|
// Cleanup - delete all endusers created during test
|
|
261
308
|
const enduserEmails = [
|
|
262
309
|
'fst-test1@tellescope.com', 'fst-test2@tellescope.com', 'fst-test3@tellescope.com',
|
|
263
310
|
'fst-test4@tellescope.com', 'fst-test5@tellescope.com', 'fst-test7@tellescope.com',
|
|
264
311
|
'fst-test8@tellescope.com', 'fst-test9@tellescope.com', 'fst-test10@tellescope.com',
|
|
312
|
+
'fst-test11@tellescope.com', 'fst-test12@tellescope.com',
|
|
265
313
|
'fst-test-prepare1@tellescope.com', 'fst-test-prepare2@tellescope.com',
|
|
266
314
|
]
|
|
267
315
|
for (const email of enduserEmails) {
|