@tellescope/sdk 1.246.2 → 1.248.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/sdk.d.ts +7 -1
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +2 -0
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/api_tests/date_string_validation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.js +142 -0
- package/lib/cjs/tests/api_tests/date_string_validation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js +243 -0
- package/lib/cjs/tests/api_tests/enduser_session_invalidation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.d.ts +13 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.js +818 -0
- package/lib/cjs/tests/api_tests/field_redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.js +429 -0
- package/lib/cjs/tests/api_tests/form_submitted_trigger.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js +273 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.js +370 -0
- package/lib/cjs/tests/api_tests/mdb_sort.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +108 -24
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +303 -180
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/sdk.d.ts +7 -1
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +2 -0
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/tests/api_tests/date_string_validation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.js +138 -0
- package/lib/esm/tests/api_tests/date_string_validation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js +239 -0
- package/lib/esm/tests/api_tests/enduser_session_invalidation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/field_redaction.test.d.ts +13 -0
- package/lib/esm/tests/api_tests/field_redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/field_redaction.test.js +814 -0
- package/lib/esm/tests/api_tests/field_redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.js +425 -0
- package/lib/esm/tests/api_tests/form_submitted_trigger.test.js.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js +269 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.js +366 -0
- package/lib/esm/tests/api_tests/mdb_sort.test.js.map +1 -0
- package/lib/esm/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js +108 -24
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +303 -180
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/sdk.ts +11 -0
- package/src/tests/api_tests/calendar_events_bulk_update.test.ts +418 -0
- package/src/tests/api_tests/date_string_validation.test.ts +107 -0
- package/src/tests/api_tests/enduser_session_invalidation.test.ts +138 -0
- package/src/tests/api_tests/field_redaction.test.ts +669 -0
- package/src/tests/api_tests/form_started_trigger.test.ts +1 -1
- package/src/tests/api_tests/form_submitted_trigger.test.ts +281 -0
- package/src/tests/api_tests/integrations_redacted.test.ts +245 -0
- package/src/tests/api_tests/mdb_sort.test.ts +259 -0
- package/src/tests/api_tests/openloop_webhooks.test.ts +64 -0
- package/src/tests/api_tests/organization_settings_duplicates.test.ts +201 -0
- package/src/tests/tests.ts +92 -6
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
async_test,
|
|
6
|
+
assert,
|
|
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
|
+
export const calendar_events_bulk_update_tests = async ({ sdk }: { sdk: Session }) => {
|
|
15
|
+
log_header("Calendar Events Bulk Update Tests")
|
|
16
|
+
|
|
17
|
+
// Track all created resources for cleanup
|
|
18
|
+
const createdEventIds: string[] = []
|
|
19
|
+
const createdEnduserIds: string[] = []
|
|
20
|
+
|
|
21
|
+
const createEvent = async (overrides: Record<string, any> = {}) => {
|
|
22
|
+
const event = await sdk.api.calendar_events.createOne({
|
|
23
|
+
title: "Bulk Update Test Event",
|
|
24
|
+
durationInMinutes: 30,
|
|
25
|
+
startTimeInMS: Date.now() + Math.random() * 100000,
|
|
26
|
+
...overrides,
|
|
27
|
+
})
|
|
28
|
+
createdEventIds.push(event.id)
|
|
29
|
+
return event
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const createEnduser = async () => {
|
|
33
|
+
const enduser = await sdk.api.endusers.createOne({})
|
|
34
|
+
createdEnduserIds.push(enduser.id)
|
|
35
|
+
return enduser
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// ============================================================
|
|
40
|
+
// SECTION 1: Validation / Error Cases
|
|
41
|
+
// ============================================================
|
|
42
|
+
log_header("Bulk Update - Validation")
|
|
43
|
+
|
|
44
|
+
await async_test(
|
|
45
|
+
"bulk_update errors when neither recurringEventId nor ids provided",
|
|
46
|
+
() => sdk.api.calendar_events.bulk_update({ action: 'cancel' } as any),
|
|
47
|
+
{ shouldError: true, onError: (e: any) => e.message.includes("Either recurringEventId or ids is required") }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const validationEvent = await createEvent()
|
|
51
|
+
|
|
52
|
+
await async_test(
|
|
53
|
+
"bulk_update errors when both recurringEventId and ids provided",
|
|
54
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
55
|
+
recurringEventId: validationEvent.id,
|
|
56
|
+
ids: [validationEvent.id],
|
|
57
|
+
action: 'cancel',
|
|
58
|
+
}),
|
|
59
|
+
{ shouldError: true, onError: (e: any) => e.message.includes("Provide either recurringEventId or ids, not both") }
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const validationEnduser = await createEnduser()
|
|
63
|
+
|
|
64
|
+
await async_test(
|
|
65
|
+
"bulk_update errors when attendee-level action used with ids",
|
|
66
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
67
|
+
ids: [validationEvent.id],
|
|
68
|
+
action: 'cancel_for_attendee',
|
|
69
|
+
enduserId: validationEnduser.id,
|
|
70
|
+
}),
|
|
71
|
+
{ shouldError: true, onError: (e: any) => e.message.includes("only supported with recurringEventId") }
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
await async_test(
|
|
75
|
+
"bulk_update errors when attendee action missing enduserId",
|
|
76
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
77
|
+
recurringEventId: validationEvent.id,
|
|
78
|
+
action: 'cancel_for_attendee',
|
|
79
|
+
}),
|
|
80
|
+
{ shouldError: true, onError: (e: any) => e.message.includes("enduserId is required") }
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// ============================================================
|
|
84
|
+
// SECTION 2: ID-based bulk operations (new feature)
|
|
85
|
+
// ============================================================
|
|
86
|
+
log_header("Bulk Update - ID-based Cancel")
|
|
87
|
+
|
|
88
|
+
const [ev1, ev2, ev3] = await Promise.all([createEvent(), createEvent(), createEvent()])
|
|
89
|
+
|
|
90
|
+
await async_test(
|
|
91
|
+
"bulk cancel by IDs cancels selected events",
|
|
92
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
93
|
+
ids: [ev1.id, ev2.id],
|
|
94
|
+
action: 'cancel',
|
|
95
|
+
cancelReason: 'Testing bulk cancel',
|
|
96
|
+
}),
|
|
97
|
+
{
|
|
98
|
+
onResult: (r: any) => (
|
|
99
|
+
r.updated.length === 2
|
|
100
|
+
&& r.updated.every((e: any) => !!e.cancelledAt)
|
|
101
|
+
&& r.updated.every((e: any) => e.cancelReason === 'Testing bulk cancel')
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Verify third event is unchanged
|
|
107
|
+
const ev3After = await sdk.api.calendar_events.getOne(ev3.id)
|
|
108
|
+
assert(!ev3After.cancelledAt, 'Third event should not be cancelled', 'Third event remains uncancelled')
|
|
109
|
+
|
|
110
|
+
// --- Uncancel ---
|
|
111
|
+
log_header("Bulk Update - ID-based Uncancel")
|
|
112
|
+
|
|
113
|
+
await async_test(
|
|
114
|
+
"bulk uncancel by IDs restores cancelled events",
|
|
115
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
116
|
+
ids: [ev1.id, ev2.id],
|
|
117
|
+
action: 'uncancel',
|
|
118
|
+
}),
|
|
119
|
+
{
|
|
120
|
+
onResult: (r: any) => (
|
|
121
|
+
r.updated.length === 2
|
|
122
|
+
&& r.updated.every((e: any) => !e.cancelledAt)
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
// --- Confirm ---
|
|
128
|
+
log_header("Bulk Update - ID-based Confirm")
|
|
129
|
+
|
|
130
|
+
await async_test(
|
|
131
|
+
"bulk confirm by IDs sets confirmedAt",
|
|
132
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
133
|
+
ids: [ev1.id, ev2.id],
|
|
134
|
+
action: 'confirm',
|
|
135
|
+
}),
|
|
136
|
+
{
|
|
137
|
+
onResult: (r: any) => (
|
|
138
|
+
r.updated.length === 2
|
|
139
|
+
&& r.updated.every((e: any) => !!e.confirmedAt)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// --- Idempotency: confirm already-confirmed ---
|
|
145
|
+
await async_test(
|
|
146
|
+
"bulk confirm on already-confirmed events returns empty updated",
|
|
147
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
148
|
+
ids: [ev1.id, ev2.id],
|
|
149
|
+
action: 'confirm',
|
|
150
|
+
}),
|
|
151
|
+
{ onResult: (r: any) => r.updated.length === 0 }
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// --- No Show ---
|
|
155
|
+
log_header("Bulk Update - ID-based No Show")
|
|
156
|
+
|
|
157
|
+
await async_test(
|
|
158
|
+
"bulk no_show by IDs sets noShowedAt",
|
|
159
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
160
|
+
ids: [ev1.id, ev2.id],
|
|
161
|
+
action: 'no_show',
|
|
162
|
+
}),
|
|
163
|
+
{
|
|
164
|
+
onResult: (r: any) => (
|
|
165
|
+
r.updated.length === 2
|
|
166
|
+
&& r.updated.every((e: any) => !!e.noShowedAt)
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// --- Un-No-Show ---
|
|
172
|
+
log_header("Bulk Update - ID-based Un-No-Show")
|
|
173
|
+
|
|
174
|
+
await async_test(
|
|
175
|
+
"bulk un_no_show by IDs clears noShowedAt",
|
|
176
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
177
|
+
ids: [ev1.id, ev2.id],
|
|
178
|
+
action: 'un_no_show',
|
|
179
|
+
}),
|
|
180
|
+
{
|
|
181
|
+
onResult: (r: any) => (
|
|
182
|
+
r.updated.length === 2
|
|
183
|
+
&& r.updated.every((e: any) => !e.noShowedAt)
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// --- Delete by IDs ---
|
|
189
|
+
log_header("Bulk Update - ID-based Delete")
|
|
190
|
+
|
|
191
|
+
const [delEv1, delEv2] = await Promise.all([createEvent(), createEvent()])
|
|
192
|
+
|
|
193
|
+
await async_test(
|
|
194
|
+
"bulk delete by IDs removes events",
|
|
195
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
196
|
+
ids: [delEv1.id, delEv2.id],
|
|
197
|
+
action: 'delete',
|
|
198
|
+
}),
|
|
199
|
+
{ onResult: (r: any) => r.deleted.length === 2 }
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Verify deleted events are gone
|
|
203
|
+
await async_test(
|
|
204
|
+
"deleted events are no longer accessible",
|
|
205
|
+
() => sdk.api.calendar_events.getOne(delEv1.id),
|
|
206
|
+
{ shouldError: true, onError: (e: any) => e.message.includes("Could not find") }
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
// Remove from cleanup tracking since already deleted
|
|
210
|
+
createdEventIds.splice(createdEventIds.indexOf(delEv1.id), 1)
|
|
211
|
+
createdEventIds.splice(createdEventIds.indexOf(delEv2.id), 1)
|
|
212
|
+
|
|
213
|
+
// ============================================================
|
|
214
|
+
// SECTION 3: Recurring series operations
|
|
215
|
+
// ============================================================
|
|
216
|
+
log_header("Bulk Update - Recurring Series")
|
|
217
|
+
|
|
218
|
+
const now = Date.now()
|
|
219
|
+
const DAY = 24 * 60 * 60 * 1000
|
|
220
|
+
const enduser = await createEnduser()
|
|
221
|
+
|
|
222
|
+
// Create a "recurring series": root event + child events with copiedFrom
|
|
223
|
+
const rootEvent = await createEvent({
|
|
224
|
+
title: "Recurring Root",
|
|
225
|
+
startTimeInMS: now - 2 * DAY, // 2 days ago
|
|
226
|
+
attendees: [{ id: enduser.id, type: 'enduser' }],
|
|
227
|
+
})
|
|
228
|
+
const childEvent1 = await createEvent({
|
|
229
|
+
title: "Recurring Child 1",
|
|
230
|
+
startTimeInMS: now - 1 * DAY, // 1 day ago
|
|
231
|
+
copiedFrom: rootEvent.id,
|
|
232
|
+
attendees: [{ id: enduser.id, type: 'enduser' }],
|
|
233
|
+
})
|
|
234
|
+
const childEvent2 = await createEvent({
|
|
235
|
+
title: "Recurring Child 2",
|
|
236
|
+
startTimeInMS: now + 1 * DAY, // tomorrow
|
|
237
|
+
copiedFrom: rootEvent.id,
|
|
238
|
+
attendees: [{ id: enduser.id, type: 'enduser' }],
|
|
239
|
+
})
|
|
240
|
+
const childEvent3 = await createEvent({
|
|
241
|
+
title: "Recurring Child 3",
|
|
242
|
+
startTimeInMS: now + 2 * DAY, // 2 days from now
|
|
243
|
+
copiedFrom: rootEvent.id,
|
|
244
|
+
attendees: [{ id: enduser.id, type: 'enduser' }],
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// --- Cancel with scope 'this_and_future' ---
|
|
248
|
+
log_header("Bulk Update - Recurring Cancel this_and_future")
|
|
249
|
+
|
|
250
|
+
await async_test(
|
|
251
|
+
"recurring cancel this_and_future cancels anchor and future events",
|
|
252
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
253
|
+
recurringEventId: childEvent2.id,
|
|
254
|
+
action: 'cancel',
|
|
255
|
+
scope: 'this_and_future',
|
|
256
|
+
}),
|
|
257
|
+
{
|
|
258
|
+
onResult: (r: any) => (
|
|
259
|
+
r.updated.length === 2
|
|
260
|
+
&& r.updated.every((e: any) => !!e.cancelledAt)
|
|
261
|
+
&& r.updated.some((e: any) => e.id === childEvent2.id)
|
|
262
|
+
&& r.updated.some((e: any) => e.id === childEvent3.id)
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
// Verify earlier events were NOT cancelled
|
|
268
|
+
const rootAfter = await sdk.api.calendar_events.getOne(rootEvent.id)
|
|
269
|
+
assert(!rootAfter.cancelledAt, 'Root should not be cancelled', 'Root event not cancelled by this_and_future')
|
|
270
|
+
const child1After = await sdk.api.calendar_events.getOne(childEvent1.id)
|
|
271
|
+
assert(!child1After.cancelledAt, 'Child 1 should not be cancelled', 'Child 1 not cancelled by this_and_future')
|
|
272
|
+
|
|
273
|
+
// --- Uncancel with scope 'all' to reset ---
|
|
274
|
+
await sdk.api.calendar_events.bulk_update({
|
|
275
|
+
recurringEventId: rootEvent.id,
|
|
276
|
+
action: 'uncancel',
|
|
277
|
+
scope: 'all',
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// --- Cancel with scope 'all' ---
|
|
281
|
+
log_header("Bulk Update - Recurring Cancel All")
|
|
282
|
+
|
|
283
|
+
await async_test(
|
|
284
|
+
"recurring cancel all cancels entire series",
|
|
285
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
286
|
+
recurringEventId: childEvent1.id,
|
|
287
|
+
action: 'cancel',
|
|
288
|
+
scope: 'all',
|
|
289
|
+
}),
|
|
290
|
+
{
|
|
291
|
+
onResult: (r: any) => (
|
|
292
|
+
r.updated.length === 4
|
|
293
|
+
&& r.updated.every((e: any) => !!e.cancelledAt)
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
// Reset: uncancel all
|
|
299
|
+
await sdk.api.calendar_events.bulk_update({
|
|
300
|
+
recurringEventId: rootEvent.id,
|
|
301
|
+
action: 'uncancel',
|
|
302
|
+
scope: 'all',
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// --- Cancel for attendee ---
|
|
306
|
+
log_header("Bulk Update - Recurring Cancel for Attendee")
|
|
307
|
+
|
|
308
|
+
await async_test(
|
|
309
|
+
"cancel_for_attendee adds enduser to cancelledGroupAttendees across series",
|
|
310
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
311
|
+
recurringEventId: rootEvent.id,
|
|
312
|
+
action: 'cancel_for_attendee',
|
|
313
|
+
scope: 'all',
|
|
314
|
+
enduserId: enduser.id,
|
|
315
|
+
}),
|
|
316
|
+
{
|
|
317
|
+
onResult: (r: any) => (
|
|
318
|
+
r.updated.length === 4
|
|
319
|
+
&& r.updated.every((e: any) =>
|
|
320
|
+
e.cancelledGroupAttendees?.some((c: any) => c.id === enduser.id)
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
// --- Uncancel for attendee ---
|
|
327
|
+
log_header("Bulk Update - Recurring Uncancel for Attendee")
|
|
328
|
+
|
|
329
|
+
await async_test(
|
|
330
|
+
"uncancel_for_attendee removes enduser from cancelledGroupAttendees",
|
|
331
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
332
|
+
recurringEventId: rootEvent.id,
|
|
333
|
+
action: 'uncancel_for_attendee',
|
|
334
|
+
scope: 'all',
|
|
335
|
+
enduserId: enduser.id,
|
|
336
|
+
}),
|
|
337
|
+
{
|
|
338
|
+
onResult: (r: any) => (
|
|
339
|
+
r.updated.length === 4
|
|
340
|
+
&& r.updated.every((e: any) =>
|
|
341
|
+
!e.cancelledGroupAttendees?.some((c: any) => c.id === enduser.id)
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
// --- Remove attendee ---
|
|
348
|
+
log_header("Bulk Update - Recurring Remove Attendee")
|
|
349
|
+
|
|
350
|
+
await async_test(
|
|
351
|
+
"remove_attendee removes enduser from attendees across series",
|
|
352
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
353
|
+
recurringEventId: rootEvent.id,
|
|
354
|
+
action: 'remove_attendee',
|
|
355
|
+
scope: 'all',
|
|
356
|
+
enduserId: enduser.id,
|
|
357
|
+
}),
|
|
358
|
+
{
|
|
359
|
+
onResult: (r: any) => (
|
|
360
|
+
r.updated.length === 4
|
|
361
|
+
&& r.updated.every((e: any) =>
|
|
362
|
+
!e.attendees?.some((a: any) => a.id === enduser.id)
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
// --- Recurring delete ---
|
|
369
|
+
log_header("Bulk Update - Recurring Delete")
|
|
370
|
+
|
|
371
|
+
await async_test(
|
|
372
|
+
"recurring delete removes all events in series",
|
|
373
|
+
() => sdk.api.calendar_events.bulk_update({
|
|
374
|
+
recurringEventId: rootEvent.id,
|
|
375
|
+
action: 'delete',
|
|
376
|
+
scope: 'all',
|
|
377
|
+
}),
|
|
378
|
+
{ onResult: (r: any) => r.deleted.length === 4 }
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
// Remove from cleanup tracking since already deleted
|
|
382
|
+
for (const id of [rootEvent.id, childEvent1.id, childEvent2.id, childEvent3.id]) {
|
|
383
|
+
const idx = createdEventIds.indexOf(id)
|
|
384
|
+
if (idx !== -1) createdEventIds.splice(idx, 1)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
} finally {
|
|
388
|
+
// Cleanup remaining resources
|
|
389
|
+
for (const id of createdEventIds) {
|
|
390
|
+
try { await sdk.api.calendar_events.deleteOne(id) } catch (_) {}
|
|
391
|
+
}
|
|
392
|
+
for (const id of createdEnduserIds) {
|
|
393
|
+
try { await sdk.api.endusers.deleteOne(id) } catch (_) {}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Allow running this test file independently
|
|
399
|
+
if (require.main === module) {
|
|
400
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
401
|
+
const sdk = new Session({ host })
|
|
402
|
+
const sdkNonAdmin = new Session({ host })
|
|
403
|
+
|
|
404
|
+
const runTests = async () => {
|
|
405
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
406
|
+
await calendar_events_bulk_update_tests({ sdk })
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
runTests()
|
|
410
|
+
.then(() => {
|
|
411
|
+
console.log("✅ Calendar events bulk update test suite completed successfully")
|
|
412
|
+
process.exit(0)
|
|
413
|
+
})
|
|
414
|
+
.catch((error) => {
|
|
415
|
+
console.error("❌ Calendar events bulk update test suite failed:", error)
|
|
416
|
+
process.exit(1)
|
|
417
|
+
})
|
|
418
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
import { is_valid_mm_dd_yyyy } from "@tellescope/validation"
|
|
10
|
+
|
|
11
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
12
|
+
|
|
13
|
+
export const date_string_validation_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
14
|
+
log_header("Date String Validation Tests (is_valid_mm_dd_yyyy)")
|
|
15
|
+
|
|
16
|
+
// --- Valid standard dates ---
|
|
17
|
+
assert(is_valid_mm_dd_yyyy('01-15-1990') === true, 'Expected 01-15-1990 to be valid', 'valid: 01-15-1990')
|
|
18
|
+
assert(is_valid_mm_dd_yyyy('12-31-2024') === true, 'Expected 12-31-2024 to be valid', 'valid: 12-31-2024')
|
|
19
|
+
assert(is_valid_mm_dd_yyyy('06-15-2000') === true, 'Expected 06-15-2000 to be valid', 'valid: 06-15-2000')
|
|
20
|
+
assert(is_valid_mm_dd_yyyy('01-01-2000') === true, 'Expected 01-01-2000 to be valid', 'valid: 01-01-2000')
|
|
21
|
+
|
|
22
|
+
// --- Leap year: Feb 29 accepted ---
|
|
23
|
+
assert(is_valid_mm_dd_yyyy('02-29-2024') === true, 'Expected 02-29-2024 to be valid (2024 is leap year, div by 4)', 'valid: 02-29-2024 (leap year)')
|
|
24
|
+
assert(is_valid_mm_dd_yyyy('02-29-2000') === true, 'Expected 02-29-2000 to be valid (2000 is leap year, div by 400)', 'valid: 02-29-2000 (leap year)')
|
|
25
|
+
assert(is_valid_mm_dd_yyyy('02-29-2400') === true, 'Expected 02-29-2400 to be valid (2400 is leap year, div by 400)', 'valid: 02-29-2400 (leap year)')
|
|
26
|
+
|
|
27
|
+
// --- Non-leap year: Feb 29 rejected ---
|
|
28
|
+
assert(is_valid_mm_dd_yyyy('02-29-2023') === false, 'Expected 02-29-2023 to be invalid (2023 is not a leap year)', 'invalid: 02-29-2023 (not leap year)')
|
|
29
|
+
assert(is_valid_mm_dd_yyyy('02-29-1900') === false, 'Expected 02-29-1900 to be invalid (1900 div by 100 but not 400)', 'invalid: 02-29-1900 (not leap year)')
|
|
30
|
+
assert(is_valid_mm_dd_yyyy('02-29-2100') === false, 'Expected 02-29-2100 to be invalid (2100 div by 100 but not 400)', 'invalid: 02-29-2100 (not leap year)')
|
|
31
|
+
assert(is_valid_mm_dd_yyyy('02-29-2025') === false, 'Expected 02-29-2025 to be invalid (2025 is not a leap year)', 'invalid: 02-29-2025 (not leap year)')
|
|
32
|
+
|
|
33
|
+
// --- Feb 30 and Feb 31 always rejected (the original bug) ---
|
|
34
|
+
assert(is_valid_mm_dd_yyyy('02-30-2024') === false, 'Expected 02-30-2024 to be invalid (Feb never has 30 days)', 'invalid: 02-30-2024 (Feb 30 leap year)')
|
|
35
|
+
assert(is_valid_mm_dd_yyyy('02-31-2024') === false, 'Expected 02-31-2024 to be invalid (Feb never has 31 days)', 'invalid: 02-31-2024 (Feb 31 leap year)')
|
|
36
|
+
assert(is_valid_mm_dd_yyyy('02-30-2023') === false, 'Expected 02-30-2023 to be invalid (Feb never has 30 days)', 'invalid: 02-30-2023 (Feb 30 non-leap)')
|
|
37
|
+
assert(is_valid_mm_dd_yyyy('02-31-2023') === false, 'Expected 02-31-2023 to be invalid (Feb never has 31 days)', 'invalid: 02-31-2023 (Feb 31 non-leap)')
|
|
38
|
+
|
|
39
|
+
// --- Feb 28 always valid ---
|
|
40
|
+
assert(is_valid_mm_dd_yyyy('02-28-2024') === true, 'Expected 02-28-2024 to be valid', 'valid: 02-28-2024')
|
|
41
|
+
assert(is_valid_mm_dd_yyyy('02-28-2023') === true, 'Expected 02-28-2023 to be valid', 'valid: 02-28-2023')
|
|
42
|
+
|
|
43
|
+
// --- 30-day months: day 31 rejected ---
|
|
44
|
+
assert(is_valid_mm_dd_yyyy('04-31-2024') === false, 'Expected 04-31-2024 to be invalid (April has 30 days)', 'invalid: 04-31 (April)')
|
|
45
|
+
assert(is_valid_mm_dd_yyyy('06-31-2024') === false, 'Expected 06-31-2024 to be invalid (June has 30 days)', 'invalid: 06-31 (June)')
|
|
46
|
+
assert(is_valid_mm_dd_yyyy('09-31-2024') === false, 'Expected 09-31-2024 to be invalid (September has 30 days)', 'invalid: 09-31 (September)')
|
|
47
|
+
assert(is_valid_mm_dd_yyyy('11-31-2024') === false, 'Expected 11-31-2024 to be invalid (November has 30 days)', 'invalid: 11-31 (November)')
|
|
48
|
+
|
|
49
|
+
// --- 30-day months: day 30 accepted ---
|
|
50
|
+
assert(is_valid_mm_dd_yyyy('04-30-2024') === true, 'Expected 04-30-2024 to be valid', 'valid: 04-30 (April)')
|
|
51
|
+
assert(is_valid_mm_dd_yyyy('06-30-2024') === true, 'Expected 06-30-2024 to be valid', 'valid: 06-30 (June)')
|
|
52
|
+
assert(is_valid_mm_dd_yyyy('09-30-2024') === true, 'Expected 09-30-2024 to be valid', 'valid: 09-30 (September)')
|
|
53
|
+
assert(is_valid_mm_dd_yyyy('11-30-2024') === true, 'Expected 11-30-2024 to be valid', 'valid: 11-30 (November)')
|
|
54
|
+
|
|
55
|
+
// --- 31-day months: day 31 accepted ---
|
|
56
|
+
assert(is_valid_mm_dd_yyyy('01-31-2024') === true, 'Expected 01-31-2024 to be valid', 'valid: 01-31 (January)')
|
|
57
|
+
assert(is_valid_mm_dd_yyyy('03-31-2024') === true, 'Expected 03-31-2024 to be valid', 'valid: 03-31 (March)')
|
|
58
|
+
assert(is_valid_mm_dd_yyyy('05-31-2024') === true, 'Expected 05-31-2024 to be valid', 'valid: 05-31 (May)')
|
|
59
|
+
assert(is_valid_mm_dd_yyyy('07-31-2024') === true, 'Expected 07-31-2024 to be valid', 'valid: 07-31 (July)')
|
|
60
|
+
assert(is_valid_mm_dd_yyyy('08-31-2024') === true, 'Expected 08-31-2024 to be valid', 'valid: 08-31 (August)')
|
|
61
|
+
assert(is_valid_mm_dd_yyyy('10-31-2024') === true, 'Expected 10-31-2024 to be valid', 'valid: 10-31 (October)')
|
|
62
|
+
assert(is_valid_mm_dd_yyyy('12-31-2024') === true, 'Expected 12-31-2024 to be valid', 'valid: 12-31 (December)')
|
|
63
|
+
|
|
64
|
+
// --- Invalid month values ---
|
|
65
|
+
assert(is_valid_mm_dd_yyyy('00-15-2024') === false, 'Expected month 00 to be invalid', 'invalid: month 00')
|
|
66
|
+
assert(is_valid_mm_dd_yyyy('13-15-2024') === false, 'Expected month 13 to be invalid', 'invalid: month 13')
|
|
67
|
+
|
|
68
|
+
// --- Invalid day values ---
|
|
69
|
+
assert(is_valid_mm_dd_yyyy('01-00-2024') === false, 'Expected day 00 to be invalid', 'invalid: day 00')
|
|
70
|
+
assert(is_valid_mm_dd_yyyy('01-32-2024') === false, 'Expected day 32 to be invalid', 'invalid: day 32')
|
|
71
|
+
|
|
72
|
+
// --- Malformed strings ---
|
|
73
|
+
assert(is_valid_mm_dd_yyyy('') === false, 'Expected empty string to be invalid', 'invalid: empty string')
|
|
74
|
+
assert(is_valid_mm_dd_yyyy('not-a-date') === false, 'Expected text to be invalid', 'invalid: text')
|
|
75
|
+
assert(is_valid_mm_dd_yyyy('2024-01-15') === false, 'Expected YYYY-MM-DD to be invalid', 'invalid: YYYY-MM-DD format')
|
|
76
|
+
assert(is_valid_mm_dd_yyyy('1-15-2024') === false, 'Expected single-digit month to be invalid', 'invalid: single-digit month')
|
|
77
|
+
assert(is_valid_mm_dd_yyyy('01/15/2024') === false, 'Expected slashes to be invalid', 'invalid: slashes')
|
|
78
|
+
assert(is_valid_mm_dd_yyyy('01-15-24') === false, 'Expected 2-digit year to be invalid', 'invalid: 2-digit year')
|
|
79
|
+
assert(is_valid_mm_dd_yyyy('01-5-2024') === false, 'Expected single-digit day to be invalid', 'invalid: single-digit day')
|
|
80
|
+
assert(is_valid_mm_dd_yyyy('01-15-20240') === false, 'Expected 5-digit year to be invalid', 'invalid: 5-digit year')
|
|
81
|
+
|
|
82
|
+
// --- Whitespace trimming ---
|
|
83
|
+
assert(is_valid_mm_dd_yyyy(' 01-15-1990 ') === true, 'Expected trimmed whitespace to be valid', 'valid: whitespace trimmed')
|
|
84
|
+
assert(is_valid_mm_dd_yyyy(' 02-28-2024 ') === true, 'Expected trimmed whitespace to be valid', 'valid: whitespace trimmed (2)')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Allow running this test file independently
|
|
88
|
+
if (require.main === module) {
|
|
89
|
+
console.log(`Using API URL: ${host}`)
|
|
90
|
+
const sdk = new Session({ host })
|
|
91
|
+
const sdkNonAdmin = new Session({ host })
|
|
92
|
+
|
|
93
|
+
const runTests = async () => {
|
|
94
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
95
|
+
await date_string_validation_tests({ sdk, sdkNonAdmin })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
runTests()
|
|
99
|
+
.then(() => {
|
|
100
|
+
console.log("Date string validation test suite completed successfully")
|
|
101
|
+
process.exit(0)
|
|
102
|
+
})
|
|
103
|
+
.catch((error) => {
|
|
104
|
+
console.error("Date string validation test suite failed:", error)
|
|
105
|
+
process.exit(1)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session, EnduserSession } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
async_test,
|
|
6
|
+
handleAnyError,
|
|
7
|
+
log_header,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@tellescope/testing"
|
|
10
|
+
import { setup_tests } from "../setup"
|
|
11
|
+
|
|
12
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
13
|
+
const businessId = '60398b1131a295e64f084ff6'
|
|
14
|
+
|
|
15
|
+
// Main test function that can be called independently
|
|
16
|
+
export const enduser_session_invalidation_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
17
|
+
log_header("Enduser Session Invalidation Tests")
|
|
18
|
+
|
|
19
|
+
// Create test enduser
|
|
20
|
+
const testEnduser = await sdk.api.endusers.createOne({ email: `session-invalidation-test-${Date.now()}@tellescope.com` })
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Generate auth token for the enduser
|
|
24
|
+
const { authToken } = await sdk.api.endusers.generate_auth_token({ id: testEnduser.id })
|
|
25
|
+
const enduserSDK = new EnduserSession({ host, authToken, businessId: sdk.userInfo.businessId })
|
|
26
|
+
|
|
27
|
+
// Test 1: Enduser authenticated before invalidation
|
|
28
|
+
await async_test(
|
|
29
|
+
'enduser authenticated before invalidation',
|
|
30
|
+
() => enduserSDK.test_authenticated(),
|
|
31
|
+
{ expectedResult: 'Authenticated!' }
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Wait to ensure time separation between token creation and invalidation
|
|
35
|
+
await wait(undefined, 2000)
|
|
36
|
+
|
|
37
|
+
// Test 2: Setting invalidateSessionsBefore rejects old token (401)
|
|
38
|
+
await async_test(
|
|
39
|
+
'setting invalidateSessionsBefore rejects old token',
|
|
40
|
+
async () => {
|
|
41
|
+
await sdk.api.endusers.updateOne(testEnduser.id, { invalidateSessionsBefore: new Date() })
|
|
42
|
+
|
|
43
|
+
// Old token should now be rejected
|
|
44
|
+
try {
|
|
45
|
+
await enduserSDK.test_authenticated()
|
|
46
|
+
return 'should have thrown'
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return 'rejected'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{ expectedResult: 'rejected' }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// Test 3: New token after invalidation works
|
|
55
|
+
await async_test(
|
|
56
|
+
'new token after invalidation works',
|
|
57
|
+
async () => {
|
|
58
|
+
// Wait to ensure new token iat is after invalidateSessionsBefore
|
|
59
|
+
await wait(undefined, 2000)
|
|
60
|
+
|
|
61
|
+
const { authToken: newAuthToken } = await sdk.api.endusers.generate_auth_token({ id: testEnduser.id })
|
|
62
|
+
const newEnduserSDK = new EnduserSession({ host, authToken: newAuthToken, businessId: sdk.userInfo.businessId })
|
|
63
|
+
|
|
64
|
+
return await newEnduserSDK.test_authenticated()
|
|
65
|
+
},
|
|
66
|
+
{ expectedResult: 'Authenticated!' }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// Test 4: Cannot set invalidateSessionsBefore backwards (constraint error)
|
|
70
|
+
await async_test(
|
|
71
|
+
'cannot set invalidateSessionsBefore backwards',
|
|
72
|
+
async () => {
|
|
73
|
+
// Try to set invalidateSessionsBefore to a date in the past (before current value)
|
|
74
|
+
const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000) // 1 day ago
|
|
75
|
+
await sdk.api.endusers.updateOne(testEnduser.id, { invalidateSessionsBefore: pastDate })
|
|
76
|
+
},
|
|
77
|
+
handleAnyError
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
// Test 5: Deleted enduser token rejected (401)
|
|
81
|
+
await async_test(
|
|
82
|
+
'deleted enduser token rejected',
|
|
83
|
+
async () => {
|
|
84
|
+
// Create a separate enduser, get token, then delete enduser
|
|
85
|
+
const tempEnduser = await sdk.api.endusers.createOne({ email: `temp-session-test-${Date.now()}@tellescope.com` })
|
|
86
|
+
const { authToken: tempAuthToken } = await sdk.api.endusers.generate_auth_token({ id: tempEnduser.id })
|
|
87
|
+
const tempEnduserSDK = new EnduserSession({ host, authToken: tempAuthToken, businessId: sdk.userInfo.businessId })
|
|
88
|
+
|
|
89
|
+
// Verify token works before deletion
|
|
90
|
+
await tempEnduserSDK.test_authenticated()
|
|
91
|
+
|
|
92
|
+
// Delete the enduser
|
|
93
|
+
await sdk.api.endusers.deleteOne(tempEnduser.id)
|
|
94
|
+
|
|
95
|
+
// Token should now be rejected
|
|
96
|
+
try {
|
|
97
|
+
await tempEnduserSDK.test_authenticated()
|
|
98
|
+
return 'should have thrown'
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return 'rejected'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{ expectedResult: 'rejected' }
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
console.log("✅ All Enduser Session Invalidation tests passed!")
|
|
107
|
+
|
|
108
|
+
} finally {
|
|
109
|
+
// Cleanup: Delete test enduser
|
|
110
|
+
try {
|
|
111
|
+
await sdk.api.endusers.deleteOne(testEnduser.id)
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Cleanup error:', error)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Allow running this test file independently
|
|
119
|
+
if (require.main === module) {
|
|
120
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
121
|
+
const sdk = new Session({ host })
|
|
122
|
+
const sdkNonAdmin = new Session({ host })
|
|
123
|
+
|
|
124
|
+
const runTests = async () => {
|
|
125
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
126
|
+
await enduser_session_invalidation_tests({ sdk, sdkNonAdmin })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
runTests()
|
|
130
|
+
.then(() => {
|
|
131
|
+
console.log("✅ Enduser session invalidation test suite completed successfully")
|
|
132
|
+
process.exit(0)
|
|
133
|
+
})
|
|
134
|
+
.catch((error) => {
|
|
135
|
+
console.error("❌ Enduser session invalidation test suite failed:", error)
|
|
136
|
+
process.exit(1)
|
|
137
|
+
})
|
|
138
|
+
}
|