@tellescope/sdk 1.256.12 → 1.256.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/sdk",
3
- "version": "1.256.12",
3
+ "version": "1.256.13",
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.12",
36
- "@tellescope/schema": "1.256.12",
37
- "@tellescope/testing": "1.256.12",
38
- "@tellescope/types-client": "1.256.12",
39
- "@tellescope/types-models": "1.256.12",
40
- "@tellescope/types-utilities": "1.256.12",
41
- "@tellescope/utilities": "1.256.12",
42
- "@tellescope/validation": "1.256.12",
35
+ "@tellescope/constants": "1.256.13",
36
+ "@tellescope/schema": "1.256.13",
37
+ "@tellescope/testing": "1.256.13",
38
+ "@tellescope/types-client": "1.256.13",
39
+ "@tellescope/types-models": "1.256.13",
40
+ "@tellescope/types-utilities": "1.256.13",
41
+ "@tellescope/utilities": "1.256.13",
42
+ "@tellescope/validation": "1.256.13",
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": "137bfca8fc8d2229a8eee245265536e507a3afae"
58
+ "gitHead": "457e037174ead2b05f585d810de05f724659430a"
59
59
  }
package/src/sdk.ts CHANGED
@@ -810,6 +810,9 @@ type Queries = { [K in keyof ClientModelForName]: APIQuery<K> } & {
810
810
  load_events: (args: extractFields<CustomActions['calendar_events']['load_events']['parameters']>) => (
811
811
  Promise<extractFields<CustomActions['calendar_events']['load_events']['returns']>>
812
812
  ),
813
+ out_of_office: (args?: extractFields<CustomActions['calendar_events']['out_of_office']['parameters']>) => (
814
+ Promise<extractFields<CustomActions['calendar_events']['out_of_office']['returns']>>
815
+ ),
813
816
  generate_zoom_meeting: (args: extractFields<CustomActions['calendar_events']['generate_zoom_meeting']['parameters']>) => (
814
817
  Promise<extractFields<CustomActions['calendar_events']['generate_zoom_meeting']['returns']>>
815
818
  ),
@@ -1146,6 +1149,7 @@ export class Session extends SessionManager {
1146
1149
  queries.calendar_events.session_for_start_link = a => this._GET(`/v1${schema.calendar_events.publicActions.session_for_start_link.path}`, a)
1147
1150
  queries.calendar_events.get_events_for_user = a => this._GET(`/v1/${schema.calendar_events.customActions.get_events_for_user.path}`, a)
1148
1151
  queries.calendar_events.load_events = a => this._GET(`/v1/${schema.calendar_events.customActions.load_events.path}`, a)
1152
+ queries.calendar_events.out_of_office = a => this._GET(`/v1${schema.calendar_events.customActions.out_of_office.path}`, a)
1149
1153
  queries.calendar_events.generate_meeting_link = a => this._POST(`/v1/${schema.calendar_events.customActions.generate_meeting_link.path}`, a)
1150
1154
  queries.calendar_events.generate_zoom_meeting = a => this._POST(`/v1/${schema.calendar_events.customActions.generate_zoom_meeting.path}`, a)
1151
1155
  queries.calendar_events.change_zoom_host = a => this._POST(`/v1/${schema.calendar_events.customActions.change_zoom_host.path}`, a)
@@ -0,0 +1,162 @@
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
+ const MS_PER_MINUTE = 60 * 1000
13
+ const MS_PER_HOUR = 60 * MS_PER_MINUTE
14
+
15
+ // Covers the calendar_events.out_of_office endpoint, whose whole reason to exist is that it reads
16
+ // ACROSS users: calendar_events is row-filtered on attendees.id and a non-admin defaults to
17
+ // read: 'Assigned', so the ordinary read path returns only the caller's own blocks. The endpoint
18
+ // therefore queries organization-wide on purpose, and returns no event content to make that safe.
19
+ //
20
+ // The cross-user assertion below is the one that matters: swapping the handler's
21
+ // buildOrganizationWideQueries back to the request-scoped DB would silently break peer visibility in
22
+ // Team Chat without breaking a build or any other test.
23
+ export const out_of_office_endpoint_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
24
+ log_header("Out of Office Endpoint")
25
+
26
+ const adminId = sdk.userInfo.id
27
+ const nonAdminId = sdkNonAdmin.userInfo.id
28
+ const now = Date.now()
29
+
30
+ // active right now, on the ADMIN, so the non-admin is neither an attendee nor the creator
31
+ const active = await sdk.api.calendar_events.createOne({
32
+ title: 'Out of Office',
33
+ outOfOffice: true,
34
+ startTimeInMS: now - 2 * MS_PER_HOUR,
35
+ durationInMinutes: 8 * 60,
36
+ attendees: [{ id: adminId, type: 'user' }],
37
+ })
38
+ // ended three hours ago — inside the query's start-time range, so only the handler's computed
39
+ // end-time check excludes it
40
+ const ended = await sdk.api.calendar_events.createOne({
41
+ title: 'Out of Office',
42
+ outOfOffice: true,
43
+ startTimeInMS: now - 5 * MS_PER_HOUR,
44
+ durationInMinutes: 2 * 60,
45
+ attendees: [{ id: adminId, type: 'user' }],
46
+ })
47
+ const cancelled = await sdk.api.calendar_events.createOne({
48
+ title: 'Out of Office',
49
+ outOfOffice: true,
50
+ startTimeInMS: now - MS_PER_HOUR,
51
+ durationInMinutes: 4 * 60,
52
+ attendees: [{ id: adminId, type: 'user' }],
53
+ cancelledAt: new Date(),
54
+ })
55
+ // not flagged: a normal event overlapping now must never appear
56
+ const normal = await sdk.api.calendar_events.createOne({
57
+ title: 'Regular Appointment',
58
+ startTimeInMS: now - MS_PER_HOUR,
59
+ durationInMinutes: 4 * 60,
60
+ attendees: [{ id: adminId, type: 'user' }],
61
+ })
62
+ // on the non-admin, used for the userIds scoping check
63
+ const nonAdminOwn = await sdk.api.calendar_events.createOne({
64
+ title: 'Out of Office',
65
+ outOfOffice: true,
66
+ startTimeInMS: now - MS_PER_HOUR,
67
+ durationInMinutes: 4 * 60,
68
+ attendees: [{ id: nonAdminId, type: 'user' }],
69
+ })
70
+
71
+ try {
72
+ const { outOfOffice } = await sdkNonAdmin.api.calendar_events.out_of_office()
73
+
74
+ // assert(assertion, failureMessage, successTitle)
75
+ assert(
76
+ !!outOfOffice.find(o => o.userId === adminId && o.startTimeInMS === active.startTimeInMS),
77
+ 'non-admin does NOT see a peer out of office window — is the handler using the request-scoped DB instead of buildOrganizationWideQueries?',
78
+ 'non-admin sees a peer out of office window',
79
+ )
80
+
81
+ assert(
82
+ outOfOffice.every(o => (
83
+ Object.keys(o).sort().join(',') === 'endTimeInMS,startTimeInMS,userId'
84
+ )),
85
+ `response leaked event fields: ${JSON.stringify(outOfOffice[0] ?? {})}`,
86
+ 'response carries no event content',
87
+ )
88
+
89
+ const forAdmin = outOfOffice.filter(o => o.userId === adminId)
90
+ assert(
91
+ forAdmin.length === 1,
92
+ `expected exactly the one active window for the admin, got ${forAdmin.length}: ${JSON.stringify(forAdmin)}`,
93
+ 'ended, cancelled and unflagged events are excluded',
94
+ )
95
+ assert(
96
+ forAdmin[0]?.endTimeInMS === active.startTimeInMS + active.durationInMinutes * MS_PER_MINUTE,
97
+ `endTimeInMS was ${forAdmin[0]?.endTimeInMS}, expected ${active.startTimeInMS + active.durationInMinutes * MS_PER_MINUTE}`,
98
+ 'endTimeInMS is resolved from startTimeInMS + durationInMinutes',
99
+ )
100
+
101
+ // An admin's reads are unrestricted anyway, so this passes even if the handler were reverted to
102
+ // the request-scoped DB — it's the non-admin assertion above that pins the design. Kept as the
103
+ // pair, so the two together show the result is role-independent rather than role-dependent.
104
+ const asAdmin = await sdk.api.calendar_events.out_of_office()
105
+ assert(
106
+ !!asAdmin.outOfOffice.find(o => o.userId === adminId && o.startTimeInMS === active.startTimeInMS)
107
+ && !!asAdmin.outOfOffice.find(o => o.userId === nonAdminId),
108
+ `admin load missing windows: ${JSON.stringify(asAdmin.outOfOffice)}`,
109
+ 'admin sees both its own and a peer out of office window',
110
+ )
111
+ assert(
112
+ asAdmin.outOfOffice.filter(o => o.userId === adminId).length === forAdmin.length,
113
+ `admin and non-admin disagree on the admin's windows: ${JSON.stringify(asAdmin.outOfOffice.filter(o => o.userId === adminId))} vs ${JSON.stringify(forAdmin)}`,
114
+ 'admin and non-admin loads return the same windows',
115
+ )
116
+
117
+ const scoped = await sdkNonAdmin.api.calendar_events.out_of_office({ userIds: [nonAdminId] })
118
+ assert(
119
+ scoped.outOfOffice.length === 1 && scoped.outOfOffice[0].userId === nonAdminId,
120
+ `expected only the requested user, got ${JSON.stringify(scoped.outOfOffice)}`,
121
+ 'userIds scopes the result',
122
+ )
123
+
124
+ // a real ObjectId that belongs to no user in this business
125
+ const foreign = await sdkNonAdmin.api.calendar_events.out_of_office({ userIds: ['5fdb3d4a4e6e2a0016a1a1a1'] })
126
+ assert(
127
+ foreign.outOfOffice.length === 0,
128
+ `expected empty for unknown userIds, got ${JSON.stringify(foreign.outOfOffice)}`,
129
+ 'unknown userIds return nothing',
130
+ )
131
+ } finally {
132
+ await Promise.all([
133
+ sdk.api.calendar_events.deleteOne(active.id),
134
+ sdk.api.calendar_events.deleteOne(ended.id),
135
+ sdk.api.calendar_events.deleteOne(cancelled.id),
136
+ sdk.api.calendar_events.deleteOne(normal.id),
137
+ sdk.api.calendar_events.deleteOne(nonAdminOwn.id),
138
+ ])
139
+ }
140
+ }
141
+
142
+ // Allow running this test file independently
143
+ if (require.main === module) {
144
+ console.log(`🌐 Using API URL: ${host}`)
145
+ const sdk = new Session({ host })
146
+ const sdkNonAdmin = new Session({ host })
147
+
148
+ const runTests = async () => {
149
+ await setup_tests(sdk, sdkNonAdmin)
150
+ await out_of_office_endpoint_tests({ sdk, sdkNonAdmin })
151
+ }
152
+
153
+ runTests()
154
+ .then(() => {
155
+ console.log("✅ Out of office endpoint test suite completed successfully")
156
+ process.exit(0)
157
+ })
158
+ .catch((error) => {
159
+ console.error("❌ Out of office endpoint test suite failed:", error)
160
+ process.exit(1)
161
+ })
162
+ }
@@ -103,6 +103,7 @@ import { outbound_chat_sent_trigger_tests } from "./api_tests/outbound_chat_sent
103
103
  import { time_tracks_tests, time_tracks_historical_tests, time_tracks_correction_tests, time_tracks_review_tests, time_tracks_lock_tests, time_tracks_edge_case_tests, time_tracks_resubmit_tests, time_tracks_appointment_duration_tests } from "./api_tests/time_tracks.test";
104
104
  import { monthly_availability_restrictions_tests } from "./api_tests/monthly_availability_restrictions.test";
105
105
  import { calendar_event_limits_tests } from "./api_tests/calendar_event_limits.test";
106
+ import { out_of_office_endpoint_tests } from "./api_tests/out_of_office_endpoint.test";
106
107
  import { appointment_link_expiration_tests } from "./api_tests/appointment_link_expiration.test";
107
108
  import { custom_aggregation_tests } from "./api_tests/custom_aggregation.test";
108
109
  import { chats_analytics_tests } from "./api_tests/chats_analytics.test";
@@ -15446,6 +15447,7 @@ const ip_address_form_tests = async () => {
15446
15447
  await time_tracks_resubmit_tests({ sdk, sdkNonAdmin })
15447
15448
  await time_tracks_appointment_duration_tests({ sdk, sdkNonAdmin })
15448
15449
  await calendar_event_limits_tests({ sdk, sdkNonAdmin })
15450
+ await out_of_office_endpoint_tests({ sdk, sdkNonAdmin })
15449
15451
  await appointment_link_expiration_tests({ sdk, sdkNonAdmin })
15450
15452
  await get_some_projection_tests({ sdk, sdkNonAdmin })
15451
15453
  await file_download_unicode_names_tests({ sdk, sdkNonAdmin })
Binary file