diff --git a/src/models/events.ts b/src/models/events.ts index bb8aad3c..96c1114c 100644 --- a/src/models/events.ts +++ b/src/models/events.ts @@ -363,7 +363,7 @@ type ReminderMethod = 'email' | 'popup' | 'sound' | 'display'; /** * Type representing the different conferencing objects. */ -type Conferencing = Details | Autocreate; +export type Conferencing = Details | Autocreate; /** * Type representing the different objects representing time and duration for events. diff --git a/src/models/index.ts b/src/models/index.ts index 570e4c43..8aa400b5 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -17,6 +17,7 @@ export * from './listQueryParams.js'; export * from './messages.js'; export * from './redirectUri.js'; export * from './response.js'; +export * from './scheduler.js'; export * from './smartCompose.js'; export * from './threads.js'; export * from './webhooks.js'; diff --git a/src/models/scheduler.ts b/src/models/scheduler.ts new file mode 100644 index 00000000..a51d1588 --- /dev/null +++ b/src/models/scheduler.ts @@ -0,0 +1,461 @@ +import { Subset } from '../utils.js'; +import { AvailabilityRules, OpenHours } from './availability.js'; +import { Conferencing } from './events.js'; + +export type BookingType = 'booking' | 'organizer-confirmation'; +export type BookingReminderType = 'email' | 'webhook'; +export type BookingRecipientType = 'host' | 'guest' | 'all'; + +export interface BookingConfirmedTemplate { + /** + * The title to replace the default 'Booking Confirmed' title. + * This doesn't change the email subject line. Only visible in emails sent to guests. + */ + title?: string; + /** + * The additional body to be appended after the default body. + * Only visible in emails sent to guests. + */ + body?: string; +} + +export interface EmailTemplate { + /** + * The URL to a custom logo that appears at the top of the booking email. + * Replaces the default Nylas logo. The URL must be publicly accessible. + */ + logo?: string; + /** + * Configurable settings specifically for booking confirmed emails. + */ + bookingConfirmed?: BookingConfirmedTemplate; +} + +export interface SchedulerSettings { + /** + * The definitions for additional fields to be displayed in the Scheduler UI. + * Guest will see the additional fields on the Scheduling Page when they book an event. + */ + additionalFields?: Record; + /** + * The number of days in the future that Scheduler is available for scheduling events. + */ + availableDaysInFuture?: number; + /** + * The minimum number of minutes in the future that a user can make a new booking. + */ + minBookingNotice?: number; + /** + * The minimum number of minutes before a booking can be cancelled. + */ + minCancellationNotice?: number; + /** + * A message about the cancellation policy to display to users when booking an event. + */ + cancellationPolicy?: string; + /** + * The URL used to reschedule bookings. This URL is included in confirmation email messages. + */ + reschedulingUrl?: string; + /** + * The URL used to cancel bookings. This URL is included in confirmation email messages. + */ + cancellationUrl?: string; + /** + * The URL used to confirm or cancel pending bookings. This URL is included in booking request email messages. + */ + organizerConfirmationUrl?: string; + /** + * The custom URL to redirect to once the booking is confirmed. + */ + confirmationRedirectUrl?: string; + /** + * If true, the option to reschedule an event is hidden in booking confirmations and email notifications. + */ + hideReschedulingOptions?: boolean; + /** + * If true, the option to cancel an event is hidden in booking confirmations and email notifications. + */ + hideCancellationOptions?: boolean; + /** + * Whether to hide the Additional guests field on the Scheduling Page. If true, guests cannot invite additional guests to the event. + */ + hideAdditionalGuests?: boolean; + /** + * Configurable settings for booking emails. + */ + emailTemplate?: EmailTemplate; +} + +export interface BookingReminder { + /** + * The reminder type. + */ + type: BookingReminderType; + /** + * The number of minutes before the event to send the reminder. + */ + minutesBeforeEvent: number; + /** + * The recipient of the reminder. + */ + recipient?: BookingRecipientType; + /** + * The subject of the email reminder. + */ + emailSubject?: string; +} + +export interface EventBooking { + /** + * The title of the event. + */ + title: string; + /** + * The description of the event. + */ + description?: string; + /** + * The location of the event. + */ + location?: string; + /** + * The timezone for displaying the times in confirmation email messages and reminders. + */ + timezone?: string; + /** + * The type of booking. If set to booking, Scheduler follows the standard booking flow and instantly creates the event. + * If set to organizer-confirmation, Scheduler creates an event marked "Pending" in the organizer's calendar and sends + * an confirmation request email to the organizer. + * The confirmation request email includes a link to a page where the organizer can confirm or cancel the booking. + */ + bookingType?: BookingType; + /** + * An object that allows you to automatically create a conference or enter conferencing details manually. + */ + conferencing?: Conferencing; + /** + * If true, Nylas doesn't send any email messages when an event is booked, cancelled, or rescheduled. + */ + disableEmails?: boolean; + /** + * The list of reminders to send to participants before the event starts. + */ + reminders?: BookingReminder[]; +} + +export interface Availability { + /** + * The total number of minutes the event should last. + */ + durationMinutes: number; + /** + * The interval between meetings in minutes. + */ + intervalMinutes?: number; + /** + * Nylas rounds each time slot to the nearest multiple of this number of minutes. + * Must be a multiple of 5. + */ + roundTo?: number; + /** + * Availability rules for the scheduling configuration. + * These rules define how Nylas calculates availability for all participants. + */ + availabilityRules?: Omit; +} + +export interface ParticipantBooking { + /** + * The calendar ID that the event is created in. + */ + calendarId: string; +} + +export interface ParticipantAvailability { + /** + * An optional list of the calendar IDs associated with each participant's email address. + * If not provided, Nylas uses the primary calendar ID. + */ + calendarIds: string[]; + /** + * Open hours for this participant. The endpoint searches for free time slots during these open hours. + */ + openHours?: OpenHours[]; +} + +/** + * Interface representing a booking participant. + */ +export interface ConfigParticipant { + /** + * Participant's email address. + */ + email: string; + /** + * The availability data for the participant. + * If omitted, the participant is considered to be available at all times. + * At least one participant must have availability data. + */ + availability: ParticipantAvailability; + /** + * The booking data for the participant. + * If omitted, the participant is not included in the booked event. + * At least one participant must have booking data. + */ + booking: ParticipantBooking; + /** + * Participant's name. + */ + name?: string; + /** + * Whether the participant is the organizer of the event. + * For non-round-robin meetings, one of the participants must be specified as an organizer. + */ + isOrganizer?: boolean; + /** + * The participant's timezone. + * This is used when calculating the participant's open hours and in email notifications. + */ + timezone?: string; +} + +export interface Configuration { + /** + * The list of participants that is included in the scheduled event. + * All participants must have a valid Nylas grant. + */ + participants: ConfigParticipant[]; + /** + * The rules that determine the available time slots for the event. + */ + availability: Availability; + /** + * The booking data for the event. + */ + eventBooking: EventBooking; + /** + * The slug of the Configuration object. This is an optional, unique identifier for the Configuration object. + */ + slug?: string; + /** + * If true, the scheduling Availability and Bookings endpoints require a valid session ID to authenticate requests when you use this configuration. + */ + requiresSessionAuth?: boolean; + /** + * The settings for the Scheduler UI. + */ + scheduler?: SchedulerSettings; + /** + * The appearance settings for the Scheduler UI. + */ + appearance?: Record; +} + +export type CreateConfigurationRequest = Configuration; +export type UpdateConfigurationRequest = Subset; + +export interface CreateSessionRequest { + /** + * The ID of the Scheduler Configuration object for the session. + * If you're using slug, you can omit this field. + */ + configurationId?: string; + /** + * The slug of the Scheduler Configuration object for the session. + * If you're using configurationId, you can omit this field. + */ + slug?: string; + /** + * The time to live for the session in minutes. + * The maximum value is 30 minutes. + */ + timeToLive?: number; +} + +export interface Session { + /** + * The ID of the session + */ + sessionId: string; +} + +/** + * The supported languages for email notifications. + */ +export type EmailLanguage = + | 'en' + | 'es' + | 'fr' + | 'de' + | 'nl' + | 'sv' + | 'ja' + | 'zh'; + +export interface BookingGuest { + /** + * The email address of the guest. + */ + email: string; + /** + * The name of the guest. + */ + name: string; +} + +export interface BookingParticipant { + /** + * The email address of the participant to include in the booking. + */ + email: string; +} + +/** + * Interface representing a create booking request. + */ +export interface CreateBookingRequest { + /** + * The event's start time, in Unix epoch format. + */ + startTime: number; + /** + * The event's end time, in Unix epoch format. + */ + endTime: number; + /** + * Details about the guest that is creating the booking. The guest name and email are required. + */ + guest: BookingGuest; + /** + * An array of objects that include a list of participant email addresses from the Configuration object to include in the booking. + * If not provided, Nylas includes all participants from the Configuration object. + */ + participants?: BookingParticipant[]; + /** + * The guest's timezone that is used in email notifications. + * If not provided, Nylas uses the timezone from the Configuration object. + */ + timezone?: string; + /** + * The language of the guest email notifications. + */ + emailLanguage?: EmailLanguage; + /** + * An array of objects that include a list of additional guest email addresses to include in the booking. + */ + additionalGuests?: BookingGuest[]; + /** + * A dictionary of additional field keys mapped to the values populated by the guest in the booking form. + */ + additionalFields?: Record; +} + +export interface BookingOrganizer { + /** + * The email address of the participant that is designated as the organizer of the event. + */ + email: string; + /** + * The name of the participant that is designated as the organizer of the event. + */ + name?: string; +} + +export type BookingStatus = 'pending' | 'booked' | 'cancelled'; +export type ConfirmBookingStatus = 'confirmed' | 'cancelled'; + +export interface Booking { + /** + * The unique ID of the booking + */ + bookingId: string; + /** + * The unique ID of the event associated with the booking + */ + eventId: string; + /** + * The title of the event + */ + title: string; + /** + * The participant that is designated as the organizer of the event + */ + organizer: BookingOrganizer; + /** + * The current status of the booking + */ + status: BookingStatus; + /** + * The description of the event + */ + description?: string; +} + +export interface ConfirmBookingRequest { + /** + * The salt extracted from the booking reference embedded in the organizer confirmation link, + * encoded as a URL-safe base64 string (without padding). + */ + salt: string; + /** + * The action to take on the pending booking + */ + status: ConfirmBookingStatus; + /** + * The reason that the booking is being cancelled + */ + cancellationReason?: string; +} + +export interface DeleteBookingRequest { + /** + * The reason that the booking is being cancelled + */ + cancellationReason?: string; +} + +export interface RescheduleBookingRequest { + /** + * The event's start time, in Unix epoch format. + */ + startTime: number; + /** + * The event's end time, in Unix epoch format. + */ + endTime: number; +} + +export interface CreateBookingQueryParams { + /** + * The ID of the Configuration object whose settings are used for calculating availability. + * If you're using session authentication (requires_session_auth is set to true), configuration_id is not required. + */ + configurationId?: string; + /** + * The slug of the Configuration object whose settings are used for calculating availability. + * If you're using session authentication (requires_session_auth is set to true) or using configurationId, slug is not required. + */ + slug?: string; + /** + * The timezone to use for the booking. If not provided, Nylas uses the timezone from the Configuration object. + */ + timezone?: string; +} + +export interface FindBookingQueryParams { + /** + * The ID of the Configuration object whose settings are used for calculating availability. + * If you're using session authentication (requires_session_auth is set to true), configuration_id is not required. + */ + configurationId?: string; + /** + * The slug of the Configuration object whose settings are used for calculating availability. + * If you're using session authentication (requires_session_auth is set to true) or using configurationId, slug is not required. + */ + slug?: string; +} + +export type ConfirmBookingQueryParams = FindBookingQueryParams; +export type RescheduleBookingQueryParams = FindBookingQueryParams; +export type DestroyBookingQueryParams = FindBookingQueryParams; diff --git a/src/nylas.ts b/src/nylas.ts index 4c321142..ea960770 100644 --- a/src/nylas.ts +++ b/src/nylas.ts @@ -15,6 +15,7 @@ import { Folders } from './resources/folders.js'; import { Grants } from './resources/grants.js'; import { Contacts } from './resources/contacts.js'; import { Attachments } from './resources/attachments.js'; +import { Scheduler } from './resources/scheduler.js'; /** * The entry point to the Node SDK @@ -75,6 +76,10 @@ export default class Nylas { * Access the Folders API */ public folders: Folders; + /** + * Access the scheduler API + */ + public scheduler: Scheduler; /** * The configured API client @@ -106,6 +111,7 @@ export default class Nylas { this.folders = new Folders(this.apiClient); this.contacts = new Contacts(this.apiClient); this.attachments = new Attachments(this.apiClient); + this.scheduler = new Scheduler(this.apiClient); return this; } diff --git a/src/resources/bookings.ts b/src/resources/bookings.ts new file mode 100644 index 00000000..b92ef936 --- /dev/null +++ b/src/resources/bookings.ts @@ -0,0 +1,164 @@ +import { Overrides } from '../config.js'; +import { NylasBaseResponse, NylasResponse } from '../models/response.js'; +import { + Booking, + ConfirmBookingQueryParams, + ConfirmBookingRequest, + CreateBookingQueryParams, + CreateBookingRequest, + DeleteBookingRequest, + DestroyBookingQueryParams, + FindBookingQueryParams, + RescheduleBookingQueryParams, + RescheduleBookingRequest, +} from '../models/scheduler.js'; +import { Resource } from './resource.js'; + +/** + * The parameters for the {@link Bookings.find} method + * @property bookingId The id of the Booking to retrieve. Use "primary" to refer to the primary booking associated with grant. + * @property identifier The identifier of the grant to act upon + * @property queryParams The query parameters to include in the request + */ +export interface FindBookingParams { + bookingId: string; + queryParams?: FindBookingQueryParams; +} + +/** + * The parameters for the {@link Bookings.create} method + * @property identifier The identifier of the grant to act upon + * @property requestBody The request body to create a booking + * @property queryParams The query parameters to include in the request + */ +export interface CreateBookingParams { + requestBody: CreateBookingRequest; + queryParams?: CreateBookingQueryParams; +} + +/** + * The parameters for the {@link Bookings.confirm} method + * @property identifier The identifier of the grant to act upon + * @property bookingId The id of the Booking to retrieve. Use "primary" to refer to the primary booking associated with grant. + * @property requestBody The values to confirm the Booking with + * @property queryParams The query parameters to include in the request + */ +export interface ConfirmBookingParams { + bookingId: string; + requestBody: ConfirmBookingRequest; + queryParams?: ConfirmBookingQueryParams; +} + +/** + * The parameters for the {@link Bookings.reschedule} method + * @property identifier The identifier of the grant to act upon + * @property bookingId The id of the Booking to retrieve. Use "primary" to refer to the primary booking associated with grant. + * @property requestBody The values to reschedule the Booking with + * @property queryParams The query parameters to include in the request + */ +export interface RescheduleBookingParams { + bookingId: string; + requestBody: RescheduleBookingRequest; + queryParams?: RescheduleBookingQueryParams; +} + +/** + * The parameters for the {@link Bookings.destroy} method + * @property identifier The identifier of the grant to act upon + * @property bookingId The id of the Booking to retrieve. Use "primary" to refer to the primary booking associated with grant. + * @property queryParams The query parameters to include in the request + */ +export interface DestroyBookingParams { + bookingId: string; + requestBody?: DeleteBookingRequest; + queryParams?: DestroyBookingQueryParams; +} + +export class Bookings extends Resource { + /** + * Return a Booking + * @return The booking + */ + public find({ + bookingId, + queryParams, + overrides, + }: FindBookingParams & Overrides): Promise> { + return super._find({ + path: `/v3/scheduling/bookings/${bookingId}`, + queryParams, + overrides, + }); + } + + /** + * Create a Booking + * @return The created booking + */ + public create({ + requestBody, + queryParams, + overrides, + }: CreateBookingParams & Overrides): Promise> { + return super._create({ + path: `/v3/scheduling/bookings`, + requestBody, + queryParams, + overrides, + }); + } + + /** + * Confirm a Booking + * @return The confirmed Booking + */ + public confirm({ + bookingId, + requestBody, + queryParams, + overrides, + }: ConfirmBookingParams & Overrides): Promise> { + return super._update({ + path: `/v3/scheduling/bookings/${bookingId}`, + requestBody, + queryParams, + overrides, + }); + } + + /** + * Reschedule a Booking + * @return The rescheduled Booking + */ + public reschedule({ + bookingId, + requestBody, + queryParams, + overrides, + }: RescheduleBookingParams & Overrides): Promise> { + return super._updatePatch({ + path: `/v3/scheduling/bookings/${bookingId}`, + requestBody, + queryParams, + overrides, + }); + } + + /** + * Delete a Booking + * @return The deleted Booking + */ + public destroy({ + bookingId, + requestBody, + queryParams, + overrides, + }: DestroyBookingParams & Overrides): Promise { + return super._destroy({ + path: `/v3/scheduling/bookings/${bookingId}`, + requestBody, + queryParams, + overrides, + }); + } +} diff --git a/src/resources/configurations.ts b/src/resources/configurations.ts new file mode 100644 index 00000000..3eac4158 --- /dev/null +++ b/src/resources/configurations.ts @@ -0,0 +1,149 @@ +import { AsyncListResponse, Resource } from './resource.js'; +import { + CreateConfigurationRequest, + UpdateConfigurationRequest, + Configuration, +} from '../models/scheduler.js'; +import { Overrides } from '../config.js'; +import { + NylasBaseResponse, + NylasListResponse, + NylasResponse, +} from '../models/response.js'; + +/** + * The parameters for the {@link Configurations.find} method + * @property configurationId The id of the Configuration to retrieve. Use "primary" to refer to the primary configuration associated with grant. + * @property identifier The identifier of the grant to act upon + */ +export interface FindConfigurationParams { + identifier: string; + configurationId: string; +} + +/** + * The parameters for the {@link Configurations.list} method + * @property identifier The identifier of the grant to act upon + * @property queryParams The query parameters to include in the request + */ +export interface ListConfigurationsParams { + identifier: string; +} + +/** + * The parameters for the {@link Configurations.create} method + * @property identifier The identifier of the grant to act upon + * @property requestBody The request body to create a configuration + */ +export interface CreateConfigurationParams { + identifier: string; + requestBody: CreateConfigurationRequest; +} + +/** + * The parameters for the {@link Configurations.update} method + * @property identifier The identifier of the grant to act upon + * @property configurationId The id of the Configuration to retrieve. Use "primary" to refer to the primary configuration associated with grant. + */ +export interface UpdateConfigurationParams { + identifier: string; + configurationId: string; + requestBody: UpdateConfigurationRequest; +} + +/** + * The parameters for the {@link Configurations.destroy} method + * @property identifier The identifier of the grant to act upon + * @property configurationId The id of the Configuration to retrieve. Use "primary" to refer to the primary configuration associated with grant. + */ +export interface DestroyConfigurationParams { + identifier: string; + configurationId: string; +} + +export class Configurations extends Resource { + /** + * Return all Configurations + * @return A list of configurations + */ + public list({ + identifier, + overrides, + }: ListConfigurationsParams & Overrides): AsyncListResponse< + NylasListResponse + > { + return super._list>({ + overrides, + path: `/v3/grants/${identifier}/scheduling/configurations`, + }); + } + + /** + * Return a Configuration + * @return The configuration + */ + public find({ + identifier, + configurationId, + overrides, + }: FindConfigurationParams & Overrides): Promise< + NylasResponse + > { + return super._find({ + path: `/v3/grants/${identifier}/scheduling/configurations/${configurationId}`, + overrides, + }); + } + + /** + * Create a Configuration + * @return The created configuration + */ + public create({ + identifier, + requestBody, + overrides, + }: CreateConfigurationParams & Overrides): Promise< + NylasResponse + > { + return super._create({ + path: `/v3/grants/${identifier}/scheduling/configurations`, + requestBody, + overrides, + }); + } + + /** + * Update a Configuration + * @return The updated Configuration + */ + public update({ + configurationId, + identifier, + requestBody, + overrides, + }: UpdateConfigurationParams & Overrides): Promise< + NylasResponse + > { + return super._update({ + path: `/v3/grants/${identifier}/scheduling/configurations/${configurationId}`, + requestBody, + overrides, + }); + } + + /** + * Delete a Configuration + * @return The deleted Configuration + */ + public destroy({ + identifier, + configurationId, + overrides, + }: DestroyConfigurationParams & Overrides): Promise { + return super._destroy({ + path: `/v3/grants/${identifier}/scheduling/configurations/${configurationId}`, + overrides, + }); + } +} diff --git a/src/resources/resource.ts b/src/resources/resource.ts index 2e597c46..c5c6b3ab 100644 --- a/src/resources/resource.ts +++ b/src/resources/resource.ts @@ -30,6 +30,7 @@ interface PayloadParams { interface DestroyParams { path: string; queryParams?: Record; + requestBody?: Record; overrides?: OverridableNylasConfig; } @@ -183,12 +184,14 @@ export class Resource { protected _destroy({ path, queryParams, + requestBody, overrides, }: DestroyParams): Promise { return this.apiClient.request({ method: 'DELETE', path, queryParams, + body: requestBody, overrides, }); } diff --git a/src/resources/scheduler.ts b/src/resources/scheduler.ts new file mode 100644 index 00000000..397f5175 --- /dev/null +++ b/src/resources/scheduler.ts @@ -0,0 +1,16 @@ +import { Configurations } from './configurations.js'; +import { Sessions } from './sessions.js'; +import { Bookings } from './bookings.js'; +import APIClient from '../apiClient.js'; + +export class Scheduler { + public configurations: Configurations; + public bookings: Bookings; + public sessions: Sessions; + + constructor(apiClient: APIClient) { + this.configurations = new Configurations(apiClient); + this.bookings = new Bookings(apiClient); + this.sessions = new Sessions(apiClient); + } +} diff --git a/src/resources/sessions.ts b/src/resources/sessions.ts new file mode 100644 index 00000000..eb9e4317 --- /dev/null +++ b/src/resources/sessions.ts @@ -0,0 +1,53 @@ +import { Overrides } from '../config.js'; +import { NylasBaseResponse, NylasResponse } from '../models/response.js'; +import { CreateSessionRequest, Session } from '../models/scheduler.js'; +import { Resource } from './resource.js'; + +/** + * The parameters for the {@link Sessions.create} method + * @property identifier The identifier of the grant to act upon + * @property requestBody The request body to create a session + */ +export interface CreateSessionParams { + requestBody: CreateSessionRequest; +} + +/** + * The parameters for the {@link Sessions.destroy} method + * @property identifier The identifier of the grant to act upon + * @property sessionId The id of the Session to retrieve. Use "primary" to refer to the primary session associated with grant. + */ +export interface DestroySessionParams { + sessionId: string; +} + +export class Sessions extends Resource { + /** + * Create a Session + * @return The created session + */ + public create({ + requestBody, + overrides, + }: CreateSessionParams & Overrides): Promise> { + return super._create({ + path: `/v3/scheduling/sessions`, + requestBody, + overrides, + }); + } + + /** + * Delete a Session + * @return The deleted Session + */ + public destroy({ + sessionId, + overrides, + }: DestroySessionParams & Overrides): Promise { + return super._destroy({ + path: `/v3/scheduling/sessions/${sessionId}`, + overrides, + }); + } +} diff --git a/tests/resources/bookings.spec.ts b/tests/resources/bookings.spec.ts new file mode 100644 index 00000000..a59f8e97 --- /dev/null +++ b/tests/resources/bookings.spec.ts @@ -0,0 +1,188 @@ +import APIClient from '../../src/apiClient'; +import { Bookings } from '../../src/resources/bookings'; +jest.mock('../src/apiClient'); + +describe('Bookings', () => { + let apiClient: jest.Mocked; + let bookings: Bookings; + + beforeAll(() => { + apiClient = new APIClient({ + apiKey: 'apiKey', + apiUri: 'https://test.api.nylas.com', + timeout: 30, + headers: {}, + }) as jest.Mocked; + + bookings = new Bookings(apiClient); + apiClient.request.mockResolvedValue({}); + }); + + describe('create', () => { + it('should call apiClient.request with the correct params', async () => { + await bookings.create({ + queryParams: { + configurationId: 'configuration123', + }, + requestBody: { + startTime: 1709643600, + endTime: 1709645400, + guest: { + name: 'Jane Doe', + email: 'jane.doe@example.com', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/v3/scheduling/bookings', + queryParams: { + configurationId: 'configuration123', + }, + body: { + startTime: 1709643600, + endTime: 1709645400, + guest: { + name: 'Jane Doe', + email: 'jane.doe@example.com', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('find', () => { + it('should call apiClient.request with the correct params', async () => { + await bookings.find({ + bookingId: 'booking123', + queryParams: { + configurationId: 'configuration123', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/scheduling/bookings/booking123', + queryParams: { + configurationId: 'configuration123', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('confirm', () => { + it('should call apiClient.request with the correct params', async () => { + await bookings.confirm({ + bookingId: 'booking123', + queryParams: { + configurationId: 'configuration123', + }, + requestBody: { + salt: '_zgLLAuk_qtcsw', + status: 'cancelled', + cancellationReason: 'I am no longer available at this time.', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'PUT', + path: '/v3/scheduling/bookings/booking123', + queryParams: { + configurationId: 'configuration123', + }, + body: { + salt: '_zgLLAuk_qtcsw', + status: 'cancelled', + cancellationReason: 'I am no longer available at this time.', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('reschedule', () => { + it('should call apiClient.request with the correct params', async () => { + await bookings.reschedule({ + bookingId: 'booking123', + queryParams: { + configurationId: 'configuration123', + }, + requestBody: { + startTime: 1708714800, + endTime: 1708722000, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'PATCH', + path: '/v3/scheduling/bookings/booking123', + queryParams: { + configurationId: 'configuration123', + }, + body: { + startTime: 1708714800, + endTime: 1708722000, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('destroy', () => { + it('should call apiClient.request with the correct params', async () => { + await bookings.destroy({ + bookingId: 'booking123', + queryParams: { + configurationId: 'configuration123', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'DELETE', + path: '/v3/scheduling/bookings/booking123', + queryParams: { + configurationId: 'configuration123', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); +}); diff --git a/tests/resources/configurations.spec.ts b/tests/resources/configurations.spec.ts new file mode 100644 index 00000000..9eefb58a --- /dev/null +++ b/tests/resources/configurations.spec.ts @@ -0,0 +1,180 @@ +import APIClient from '../../src/apiClient'; +import { Configurations } from '../../src/resources/configurations'; +jest.mock('../src/apiClient'); + +describe('Configurations', () => { + let apiClient: jest.Mocked; + let configurations: Configurations; + + beforeAll(() => { + apiClient = new APIClient({ + apiKey: 'apiKey', + apiUri: 'https://test.api.nylas.com', + timeout: 30, + headers: {}, + }) as jest.Mocked; + + configurations = new Configurations(apiClient); + apiClient.request.mockResolvedValue({}); + }); + + describe('list', () => { + it('should call apiClient.request with correct params', async () => { + await configurations.list({ + identifier: 'grant123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/grant123/scheduling/configurations', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('find', () => { + it('should call apiClient.request with correct params', async () => { + await configurations.find({ + identifier: 'grant123', + configurationId: 'configuration123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/grant123/scheduling/configurations/configuration123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('create', () => { + it('should call apiClient.request with correct params', async () => { + await configurations.create({ + identifier: 'grant123', + requestBody: { + requiresSessionAuth: false, + participants: [ + { + name: 'Test', + email: 'nylassdk@nylas.com', + availability: { + calendarIds: ['primary'], + }, + booking: { + calendarId: 'primary', + }, + }, + ], + availability: { + durationMinutes: 30, + }, + eventBooking: { + title: 'My test event', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/v3/grants/grant123/scheduling/configurations', + body: { + requiresSessionAuth: false, + participants: [ + { + name: 'Test', + email: 'nylassdk@nylas.com', + availability: { + calendarIds: ['primary'], + }, + booking: { + calendarId: 'primary', + }, + }, + ], + availability: { + durationMinutes: 30, + }, + eventBooking: { + title: 'My test event', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('update', () => { + it('should call apiClient.request with the correct params', async () => { + await configurations.update({ + identifier: 'grant123', + configurationId: 'configuration123', + requestBody: { + eventBooking: { + title: 'Changed Title', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'PUT', + path: '/v3/grants/grant123/scheduling/configurations/configuration123', + body: { + eventBooking: { + title: 'Changed Title', + }, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('destroy', () => { + it('should call apiClient.request with the correct params', async () => { + await configurations.destroy({ + identifier: 'grant123', + configurationId: 'configuration123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'DELETE', + path: '/v3/grants/grant123/scheduling/configurations/configuration123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); +}); diff --git a/tests/resources/sessions.spec.ts b/tests/resources/sessions.spec.ts new file mode 100644 index 00000000..cb542a44 --- /dev/null +++ b/tests/resources/sessions.spec.ts @@ -0,0 +1,69 @@ +import APIClient from '../../src/apiClient'; +import { Sessions } from '../../src/resources/sessions'; +jest.mock('../src/apiClient'); + +describe('Sessions', () => { + let apiClient: jest.Mocked; + let sessions: Sessions; + + beforeAll(() => { + apiClient = new APIClient({ + apiKey: 'apiKey', + apiUri: 'https://test.api.nylas.com', + timeout: 30, + headers: {}, + }) as jest.Mocked; + + sessions = new Sessions(apiClient); + apiClient.request.mockResolvedValue({}); + }); + + describe('create', () => { + it('should call apiClient.request with the correct params', async () => { + await sessions.create({ + requestBody: { + configurationId: 'configuration123', + timeToLive: 30, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/v3/scheduling/sessions', + body: { + configurationId: 'configuration123', + timeToLive: 30, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); + + describe('destroy', () => { + it('should call apiClient.request with the correct params', async () => { + await sessions.destroy({ + sessionId: 'session123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'DELETE', + path: '/v3/scheduling/sessions/session123', + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'foobar' }, + }, + }); + }); + }); +});