diff --git a/lib/types/SmartsheetClient.ts b/lib/types/SmartsheetClient.ts index d44e86e..1fcb424 100644 --- a/lib/types/SmartsheetClient.ts +++ b/lib/types/SmartsheetClient.ts @@ -5,6 +5,7 @@ import type { SharingApi } from '../sharing'; import type { SightsApi } from '../sights/types'; import type { AlternateEmailsApi } from '../users/alternateemails_types'; import type { UsersApi } from '../users/types'; +import type { WebhooksApi } from '../webhooks/types'; export interface SmartsheetClient { constants: any; @@ -25,6 +26,6 @@ export interface SmartsheetClient { templates: any; tokens: any; users: UsersApi & AlternateEmailsApi; - webhooks: any; + webhooks: WebhooksApi; workspaces: any; } diff --git a/lib/webhooks/index.js b/lib/webhooks/index.js deleted file mode 100644 index d7f4c08..0000000 --- a/lib/webhooks/index.js +++ /dev/null @@ -1,53 +0,0 @@ -var _ = require('underscore'); - -exports.create = function (options) { - var requestor = options.requestor; - - var optionsToSend = _.extend({}, options.clientOptions); - - var createWebhook = (postOptions, callback) => { - var urlOptions = { url: buildUrl() }; - return requestor.post(_.extend({}, optionsToSend, urlOptions, postOptions), callback); - }; - - var deleteWebhook = (deleteOptions, callback) => { - var urlOptions = { url: buildUrl(deleteOptions.webhookId) }; - return requestor.delete(_.extend({}, optionsToSend, urlOptions, deleteOptions), callback); - }; - - var updateWebhook = (putOptions, callback) => { - var urlOptions = { url: buildUrl(putOptions.webhookId) }; - return requestor.put(_.extend({}, optionsToSend, urlOptions, putOptions), callback); - }; - - var getWebhook = (getOptions, callback) => { - var urlOptions = { url: buildUrl(getOptions.webhookId) }; - return requestor.get(_.extend({}, optionsToSend, urlOptions, getOptions), callback); - }; - - var listWebhooks = (getOptions, callback) => { - var urlOptions = { url: buildUrl() }; - return requestor.get(_.extend({}, optionsToSend, urlOptions, getOptions), callback); - }; - - var resetSharedSecret = (postOptions, callback) => { - var urlOptions = { url: buildUrl(postOptions.webhookId) + '/resetsharedsecret' }; - return requestor.post(_.extend({}, optionsToSend, urlOptions, postOptions), callback); - }; - - var buildUrl = (webhookId) => { - if (webhookId !== undefined) { - return options.apiUrls.webhooks + '/' + webhookId; - } - return options.apiUrls.webhooks; - }; - - return { - createWebhook: createWebhook, - getWebhook: getWebhook, - listWebhooks: listWebhooks, - deleteWebhook: deleteWebhook, - updateWebhook: updateWebhook, - resetSharedSecret: resetSharedSecret, - }; -}; diff --git a/lib/webhooks/index.ts b/lib/webhooks/index.ts new file mode 100644 index 0000000..4d803aa --- /dev/null +++ b/lib/webhooks/index.ts @@ -0,0 +1,79 @@ +import type { CreateOptions, RequestCallback, RequestOptions } from '../types'; +import type { + WebhooksApi, + CreateWebhookResponse, + GetWebhookOptions, + ListWebhooksResponse, + UpdateWebhookOptions, + UpdateWebhookResponse, + DeleteWebhookOptions, + DeleteWebhookResponse, + ResetSharedSecretOptions, + ResetSharedSecretResponse, + CreateWebhookBody, + Webhook, + ListWebhooksQueryParameters, +} from './types'; + +export function create(options: CreateOptions): WebhooksApi { + const requestor = options.requestor; + + const optionsToSend = { + ...options.clientOptions, + }; + + const createWebhook = ( + postOptions: RequestOptions, + callback?: RequestCallback + ) => { + const urlOptions = { url: buildUrl() }; + return requestor.post({ ...optionsToSend, ...urlOptions, ...postOptions }, callback); + }; + + const deleteWebhook = (deleteOptions: DeleteWebhookOptions, callback?: RequestCallback) => { + const urlOptions = { url: buildUrl(deleteOptions.webhookId) }; + return requestor.delete({ ...optionsToSend, ...urlOptions, ...deleteOptions }, callback); + }; + + const updateWebhook = (putOptions: UpdateWebhookOptions, callback?: RequestCallback) => { + const urlOptions = { url: buildUrl(putOptions.webhookId) }; + return requestor.put({ ...optionsToSend, ...urlOptions, ...putOptions }, callback); + }; + + const getWebhook = (getOptions: GetWebhookOptions, callback?: RequestCallback) => { + const urlOptions = { url: buildUrl(getOptions.webhookId) }; + return requestor.get({ ...optionsToSend, ...urlOptions, ...getOptions }, callback); + }; + + const listWebhooks = ( + getOptions: RequestOptions, + callback?: RequestCallback + ) => { + const urlOptions = { url: buildUrl() }; + return requestor.get({ ...optionsToSend, ...urlOptions, ...getOptions }, callback); + }; + + const resetSharedSecret = ( + postOptions: ResetSharedSecretOptions, + callback?: RequestCallback + ) => { + const urlOptions = { url: buildUrl(postOptions.webhookId) + '/resetsharedsecret' }; + return requestor.post({ ...optionsToSend, ...urlOptions, ...postOptions }, callback); + }; + + const buildUrl = (webhookId?: number) => { + if (webhookId !== undefined) { + return options.apiUrls.webhooks + '/' + webhookId; + } + return options.apiUrls.webhooks; + }; + + return { + createWebhook: createWebhook, + getWebhook: getWebhook, + listWebhooks: listWebhooks, + deleteWebhook: deleteWebhook, + updateWebhook: updateWebhook, + resetSharedSecret: resetSharedSecret, + }; +} diff --git a/lib/webhooks/types.ts b/lib/webhooks/types.ts new file mode 100644 index 0000000..a78fdd6 --- /dev/null +++ b/lib/webhooks/types.ts @@ -0,0 +1,551 @@ +import type { RequestCallback } from '../types/RequestCallback'; +import type { RequestOptions } from '../types/RequestOptions'; +import type { BaseResponseStatus } from '../types/BaseResponseStatus'; +import type { ApiError } from '../types/ApiError'; + +// ============================================================================ +// Webhooks API Interface +// ============================================================================ + +export interface WebhooksApi { + /** + * Creates a new webhook. + * + * On creation, a webhook is inactive by default. You can activate the webhook by calling + * the Update webhook operation on it with `enabled` set to `true`. + * + * @param options - {@link CreateWebhookOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link CreateWebhookResponse}\> - Optional callback function + * @returns Promise\<{@link CreateWebhookResponse}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `POST /webhooks` + * + * @example + * ```typescript + * const webhook = await client.webhooks.createWebhook({ + * body: { + * name: 'Webhook #4', + * callbackUrl: 'https://www.myApp.com/webhooks', + * scope: 'sheet', + * scopeObjectId: 3285357287499652, + * events: ['*.*'], + * version: 1 + * } + * }); + * ``` + */ + createWebhook: ( + options: RequestOptions, + callback?: RequestCallback + ) => Promise; + + /** + * Gets the specified webhook. + * + * @param options - {@link GetWebhookOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link GetWebhookResponse}\> - Optional callback function + * @returns Promise\<{@link GetWebhookResponse}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `GET /webhooks/{webhookId}` + * + * @example + * ```typescript + * const webhook = await client.webhooks.getWebhook({ + * webhookId: 401090454808452 + * }); + * ``` + */ + getWebhook: (options: GetWebhookOptions, callback?: RequestCallback) => Promise; + + /** + * Gets a list of all webhooks that the user owns. + * + * @param options - {@link ListWebhooksOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link ListWebhooksResponse}\> - Optional callback function + * @returns Promise\<{@link ListWebhooksResponse}\> + * + * @remarks + * **DEPRECATION - As early as the sunset date specified in the Changelog, webhooks will be sorted by + * creation date (most recent first) instead of name.** + * + * **Note: In the response, each webhook's `events` field defaults to `["*.*"]`, regardless of its actual value.** + * Alternatively, call GET /webhook/\{webhookId\} on an individual webhook to get its `events` value. + * + * It mirrors to the following Smartsheet REST API method: `GET /webhooks` + * + * @example + * ```typescript + * const webhooks = await client.webhooks.listWebhooks({}); + * ``` + */ + listWebhooks: ( + options: RequestOptions, + callback?: RequestCallback + ) => Promise; + + /** + * Updates the specified webhook. + * + * If you set `enabled` to `true`, the behavior and result depend on the webhook's `status` + * and may trigger a webhook verification or, in some cases, cause an error. + * + * @param options - {@link UpdateWebhookOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link UpdateWebhookResponse}\> - Optional callback function + * @returns Promise\<{@link UpdateWebhookResponse}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `PUT /webhooks/{webhookId}` + * + * @example + * ```typescript + * const result = await client.webhooks.updateWebhook({ + * webhookId: 8444254503626628, + * body: { + * enabled: true + * } + * }); + * ``` + */ + updateWebhook: ( + options: UpdateWebhookOptions, + callback?: RequestCallback + ) => Promise; + + /** + * Permanently deletes the specified webhook. + * + * This operation permanently deletes the webhook. Alternatively, to temporarily disable the webhook, + * use the Update webhook operation with `enabled` set to `false`. + * + * @param options - {@link DeleteWebhookOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link BaseResponseStatus}\> - Optional callback function + * @returns Promise\<{@link BaseResponseStatus}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `DELETE /webhooks/{webhookId}` + * + * @example + * ```typescript + * const result = await client.webhooks.deleteWebhook({ + * webhookId: 401090454808452 + * }); + * ``` + */ + deleteWebhook: ( + options: DeleteWebhookOptions, + callback?: RequestCallback + ) => Promise; + + /** + * Resets the shared secret for the specified webhook. + * + * You can improve security by using this operation to rotate an API client webhooks' shared secrets + * at periodic intervals. + * + * @param options - {@link ResetSharedSecretOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link ResetSharedSecretResponse}\> - Optional callback function + * @returns Promise\<{@link ResetSharedSecretResponse}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `POST /webhooks/{webhookId}/resetsharedsecret` + * + * @example + * ```typescript + * const result = await client.webhooks.resetSharedSecret({ + * webhookId: 401090454808452 + * }); + * ``` + */ + resetSharedSecret: ( + options: ResetSharedSecretOptions, + callback?: RequestCallback + ) => Promise; +} + +// ============================================================================ +// Webhook Types +// ============================================================================ + +/** + * Webhook scope types + */ +export type WebhookScope = 'sheet' | 'plan'; + +/** + * Webhook status types + */ +export enum WebhookStatus { + DISABLED_ADMINISTRATIVE = 'DISABLED_ADMINISTRATIVE', + DISABLED_APP_REVOKED = 'DISABLED_APP_REVOKED', + DISABLED_BY_OWNER = 'DISABLED_BY_OWNER', + DISABLED_CALLBACK_FAILED = 'DISABLED_CALLBACK_FAILED', + DISABLED_EXCEEDED_GRID_LIMITS = 'DISABLED_EXCEEDED_GRID_LIMITS', + DISABLED_SCOPE_INACCESSIBLE = 'DISABLED_SCOPE_INACCESSIBLE', + DISABLED_VERIFICATION_FAILED = 'DISABLED_VERIFICATION_FAILED', + ENABLED = 'ENABLED', + NEW_NOT_VERIFIED = 'NEW_NOT_VERIFIED', +} + +/** + * Webhook statistics + */ +export interface WebhookStats { + /** + * Timestamp of the last callback attempt + */ + lastCallbackAttempt?: Date | string; + /** + * The number of retries the webhook had performed as of the last callback attempt + */ + lastCallbackAttemptRetryCount?: number; + /** + * Timestamp of the last successful callback + */ + lastSuccessfulCallback?: Date | string; +} + +/** + * Webhook subscope for sheet-level webhooks + */ +export interface WebhookSubscope { + /** + * Array of IDs of the sheet columns to monitor + */ + columnIds?: number[]; +} + +/** + * Webhook object + */ +export interface Webhook { + /** + * Webhook Id + */ + id?: number; + /** + * Webhook name + */ + name: string; + /** + * The HTTPS URL where callbacks are sent + */ + callbackUrl: string; + /** + * The scope of the webhook (sheet or plan) + */ + scope: WebhookScope; + /** + * The Id of the object that is subscribed to (sheet Id or plan Id) + */ + scopeObjectId: number; + /** + * Array of events to subscribe to. Use ['*.*'] to subscribe to all events. + */ + events: string[]; + /** + * Webhook version (currently only version 1 is supported) + */ + version: number; + /** + * Subscope for sheet-level webhooks (limits the webhook to monitor specific columns) + * @see WebhookSubscope + */ + subscope?: WebhookSubscope; + /** + * Whether the webhook is enabled. If true, the webhook is activated; Otherwise, it's inactive or deactivated. + */ + enabled?: boolean; + /** + * Webhook statistics + * @see WebhookStats + */ + stats?: WebhookStats; + /** + * Timestamp when the webhook was created + */ + createdAt?: Date | string; + /** + * Timestamp when the webhook was last modified + */ + modifiedAt?: Date | string; + /** + * The shared secret for verifying webhook callbacks + */ + sharedSecret?: string; + /** + * Status of the webhook + * @see WebhookStatus + */ + status?: WebhookStatus | string; + /** + * Details about the reason the webhook was disabled. Only present when enabled=false. + */ + disabledDetails?: string; + /** + * ID of the corresponding third-party app that created the webhook. Only present if created by a third-party app. + */ + apiClientId?: string; + /** + * API client name corresponding to third-party app that created the webhook. Only present if created by a third-party app. + */ + apiClientName?: string; + /** + * Custom headers for plan-level webhooks. A set of custom headers that your webhook sends in all requests to your callback URL. + */ + customHeaders?: Record; +} + +/** + * Shared secret object returned when resetting shared secret + */ +export interface SharedSecret { + /** + * The new shared secret value + */ + sharedSecret: string; +} + +// ============================================================================ +// Create Webhook +// ============================================================================ + +export interface CreateWebhookBody { + /** + * Webhook name + */ + name: string; + /** + * The HTTPS URL where callbacks are sent + */ + callbackUrl: string; + /** + * The scope of the webhook (sheet or plan) + */ + scope: WebhookScope; + /** + * The Id of the object that is subscribed to (sheet Id or plan Id) + */ + scopeObjectId: number; + /** + * Array of events to subscribe to. Use ['*.*'] to subscribe to all events. + */ + events: string[]; + /** + * Webhook version (currently only version 1 is supported) + */ + version: number; + /** + * Subscope for sheet-level webhooks (limits the webhook to monitor specific columns) + * @see WebhookSubscope + */ + subscope?: WebhookSubscope; + /** + * Custom headers for plan-level webhooks + */ + customHeaders?: Record; +} + +export interface CreateWebhookResponse extends BaseResponseStatus { + /** + * Version number + */ + version?: number; + /** + * Array of failed items (if any) + * @see FailedItem + */ + failedItems?: FailedItem[]; + /** + * The created webhook object + * @see Webhook + */ + result: Webhook; +} + +// ============================================================================ +// Get Webhook +// ============================================================================ + +export interface GetWebhookOptions extends RequestOptions { + /** + * Webhook Id + */ + webhookId: number; +} + +// ============================================================================ +// List Webhooks +// ============================================================================ + +export interface ListWebhooksQueryParameters { + /** + * @defaultValue false + * @deprecated As early as the sunset date specified in the Changelog, this parameter will be discontinued + * If true, include all results (do not paginate) + */ + includeAll?: boolean; + /** + * @defaultValue 1 + * @deprecated As early as the sunset date specified in the Changelog + * Which page to return + */ + page?: number; + /** + * @defaultValue 100 + * @deprecated As early as the sunset date specified in the Changelog, the unlimited value range will be discontinued + * The maximum number of items to return per page + */ + pageSize?: number; +} + +export interface ListWebhooksResponse { + /** + * @defaultValue 1 + * The current page number + */ + pageNumber?: number; + /** + * @defaultValue 100 + * The number of items per page + */ + pageSize?: number; + /** + * @deprecated As early as the sunset date specified in the Changelog, this response property value will be `-1` + * The total number of pages + */ + totalPages?: number; + /** + * @deprecated As early as the sunset date specified in the Changelog, this response property value will be `-1` + * The total number of webhooks + */ + totalCount?: number; + /** + * Array of Webhook objects + * @see Webhook + */ + data: Webhook[]; +} + +// ============================================================================ +// Update Webhook +// ============================================================================ + +export interface UpdateWebhookBody { + /** + * Webhook name + */ + name?: string; + /** + * If `true`, the webhook is activated; Otherwise, it's inactive or deactivated. + */ + enabled?: boolean; + /** + * Array of events to subscribe to + */ + events?: string[]; + /** + * The HTTPS URL where callbacks are sent + */ + callbackUrl?: string; + /** + * Webhook version + */ + version?: number; + /** + * Custom headers for plan-level webhooks + */ + customHeaders?: Record; +} + +export interface UpdateWebhookOptions extends RequestOptions { + /** + * Webhook Id + */ + webhookId: number; +} + +export interface UpdateWebhookResponse extends BaseResponseStatus { + /** + * Version number + */ + version?: number; + /** + * Array of failed items (if any) + * @see FailedItem + */ + failedItems?: FailedItem[]; + /** + * The updated webhook object + * @see Webhook + */ + result: Webhook; +} + +// ============================================================================ +// Delete Webhook +// ============================================================================ + +export interface DeleteWebhookOptions extends RequestOptions { + /** + * Webhook Id + */ + webhookId: number; +} + +export interface FailedItem { + /** + * Row Id of the failed item + */ + rowId?: number; + /** + * Error details + * @see ApiError + */ + error: ApiError; + /** + * Index of the failed item + */ + index?: number; +} + +export interface DeleteWebhookResponse extends BaseResponseStatus { + /** + * Version number + */ + version?: number; + /** + * Array of failed items (if any) + * @see FailedItem + */ + failedItems?: FailedItem[]; +} + +// ============================================================================ +// Reset Shared Secret +// ============================================================================ + +export interface ResetSharedSecretOptions extends RequestOptions { + /** + * Webhook Id + */ + webhookId: number; +} + +export interface ResetSharedSecretResponse extends BaseResponseStatus { + /** + * Version number + */ + version?: number; + /** + * Array of failed items (if any) + * @see FailedItem + */ + failedItems?: FailedItem[]; + /** + * The shared secret object with the new shared secret + * @see SharedSecret + */ + result: SharedSecret; +} diff --git a/test/mock-api/users/common_test_constants.js b/test/mock-api/users/common_test_constants.js index 1224a6c..8306fd6 100644 --- a/test/mock-api/users/common_test_constants.js +++ b/test/mock-api/users/common_test_constants.js @@ -46,6 +46,8 @@ const ERROR_500_MESSAGE = 'Internal Server Error'; const ERROR_400_STATUS_CODE = 400; const ERROR_400_MESSAGE = 'Malformed Request'; +const ADD_PROFILE_IMAGE_REQUEST_BODY = Buffer.from('fake-image-data'); + module.exports = { TEST_USER_ID, TEST_PLAN_ID, @@ -74,5 +76,6 @@ module.exports = { ERROR_500_STATUS_CODE, ERROR_500_MESSAGE, ERROR_400_STATUS_CODE, - ERROR_400_MESSAGE + ERROR_400_MESSAGE, + ADD_PROFILE_IMAGE_REQUEST_BODY }; diff --git a/test/mock-api/users/test_add_alternate_email.js b/test/mock-api/users/test_add_alternate_email_test.js similarity index 100% rename from test/mock-api/users/test_add_alternate_email.js rename to test/mock-api/users/test_add_alternate_email_test.js diff --git a/test/mock-api/users/test_add_profile_image.js b/test/mock-api/users/test_add_profile_image_test.js similarity index 86% rename from test/mock-api/users/test_add_profile_image.js rename to test/mock-api/users/test_add_profile_image_test.js index 3205239..d2ab002 100644 --- a/test/mock-api/users/test_add_profile_image.js +++ b/test/mock-api/users/test_add_profile_image_test.js @@ -15,7 +15,8 @@ const { ERROR_500_STATUS_CODE, ERROR_500_MESSAGE, ERROR_400_STATUS_CODE, - ERROR_400_MESSAGE + ERROR_400_MESSAGE, + ADD_PROFILE_IMAGE_REQUEST_BODY } = require('./common_test_constants.js'); describe('Users - addProfileImage endpoint tests', function () { @@ -30,12 +31,9 @@ describe('Users - addProfileImage endpoint tests', function () { it('addProfileImage generated url is correct', async function () { const requestId = crypto.randomUUID(); - const mockImageBuffer = Buffer.from('fake-image-data'); const options = { userId: TEST_USER_ID, - body: { - file: mockImageBuffer - }, + fileStream: ADD_PROFILE_IMAGE_REQUEST_BODY, customProperties: { 'x-request-id': requestId, 'x-test-name': '/users/add-profile-image/all-response-body-properties' @@ -49,12 +47,9 @@ describe('Users - addProfileImage endpoint tests', function () { it('addProfileImage all response body properties', async function () { const requestId = crypto.randomUUID(); - const mockImageBuffer = Buffer.from('fake-image-data'); const options = { userId: TEST_USER_ID, - body: { - file: mockImageBuffer - }, + fileStream: ADD_PROFILE_IMAGE_REQUEST_BODY, customProperties: { 'x-request-id': requestId, 'x-test-name': '/users/add-profile-image/all-response-body-properties' @@ -62,7 +57,7 @@ describe('Users - addProfileImage endpoint tests', function () { }; const response = await client.users.addProfileImage(options); const matchedRequest = await findWireMockRequest(requestId); - + assert.ok(response); assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); @@ -77,17 +72,16 @@ describe('Users - addProfileImage endpoint tests', function () { assert.strictEqual(response.data[0].profileImage.imageId, imageId); assert.strictEqual(response.data[0].profileImage.height, height); assert.strictEqual(response.data[0].profileImage.width, width); - assert.strictEqual(matchedRequest.body.includes('fake-image-data'), true); + + const expectedBody = ADD_PROFILE_IMAGE_REQUEST_BODY.toString(); + assert.deepStrictEqual(matchedRequest.body, expectedBody); }); it('addProfileImage error 500 response', async function () { const requestId = crypto.randomUUID(); - const mockImageBuffer = Buffer.from('fake-image-data'); const options = { userId: TEST_USER_ID, - body: { - file: mockImageBuffer - }, + fileStream: ADD_PROFILE_IMAGE_REQUEST_BODY, customProperties: { 'x-request-id': requestId, 'x-test-name': '/errors/500-response' @@ -104,12 +98,9 @@ describe('Users - addProfileImage endpoint tests', function () { it('addProfileImage error 400 response', async function () { const requestId = crypto.randomUUID(); - const mockImageBuffer = Buffer.from('fake-image-data'); const options = { userId: TEST_USER_ID, - body: { - file: mockImageBuffer - }, + fileStream: ADD_PROFILE_IMAGE_REQUEST_BODY, customProperties: { 'x-request-id': requestId, 'x-test-name': '/errors/400-response' diff --git a/test/mock-api/users/test_add_user.js b/test/mock-api/users/test_add_user_test.js similarity index 100% rename from test/mock-api/users/test_add_user.js rename to test/mock-api/users/test_add_user_test.js diff --git a/test/mock-api/users/test_deactivate_reactivate_user.js b/test/mock-api/users/test_deactivate_reactivate_user_test.js similarity index 100% rename from test/mock-api/users/test_deactivate_reactivate_user.js rename to test/mock-api/users/test_deactivate_reactivate_user_test.js diff --git a/test/mock-api/users/test_delete_alternate_email.js b/test/mock-api/users/test_delete_alternate_email_test.js similarity index 100% rename from test/mock-api/users/test_delete_alternate_email.js rename to test/mock-api/users/test_delete_alternate_email_test.js diff --git a/test/mock-api/users/test_get_alternate_email.js b/test/mock-api/users/test_get_alternate_email_test.js similarity index 100% rename from test/mock-api/users/test_get_alternate_email.js rename to test/mock-api/users/test_get_alternate_email_test.js diff --git a/test/mock-api/users/test_get_current_user.js b/test/mock-api/users/test_get_current_user_test.js similarity index 100% rename from test/mock-api/users/test_get_current_user.js rename to test/mock-api/users/test_get_current_user_test.js diff --git a/test/mock-api/users/test_get_user.js b/test/mock-api/users/test_get_user_test.js similarity index 100% rename from test/mock-api/users/test_get_user.js rename to test/mock-api/users/test_get_user_test.js diff --git a/test/mock-api/users/test_list_alternate_emails.js b/test/mock-api/users/test_list_alternate_emails_test.js similarity index 100% rename from test/mock-api/users/test_list_alternate_emails.js rename to test/mock-api/users/test_list_alternate_emails_test.js diff --git a/test/mock-api/users/test_list_user_plans.js b/test/mock-api/users/test_list_user_plans_test.js similarity index 100% rename from test/mock-api/users/test_list_user_plans.js rename to test/mock-api/users/test_list_user_plans_test.js diff --git a/test/mock-api/users/test_list_users.js b/test/mock-api/users/test_list_users_test.js similarity index 100% rename from test/mock-api/users/test_list_users.js rename to test/mock-api/users/test_list_users_test.js diff --git a/test/mock-api/users/test_make_alternate_email_primary.js b/test/mock-api/users/test_make_alternate_email_primary_test.js similarity index 100% rename from test/mock-api/users/test_make_alternate_email_primary.js rename to test/mock-api/users/test_make_alternate_email_primary_test.js diff --git a/test/mock-api/users/test_remove_user_from_plan.js b/test/mock-api/users/test_remove_user_from_plan_test.js similarity index 100% rename from test/mock-api/users/test_remove_user_from_plan.js rename to test/mock-api/users/test_remove_user_from_plan_test.js diff --git a/test/mock-api/users/test_remove_user.js b/test/mock-api/users/test_remove_user_test.js similarity index 100% rename from test/mock-api/users/test_remove_user.js rename to test/mock-api/users/test_remove_user_test.js diff --git a/test/mock-api/users/test_update_user.js b/test/mock-api/users/test_update_user_test.js similarity index 100% rename from test/mock-api/users/test_update_user.js rename to test/mock-api/users/test_update_user_test.js diff --git a/test/mock-api/users/test_user_upgrade_downgrade.js b/test/mock-api/users/test_user_upgrade_downgrade_test.js similarity index 100% rename from test/mock-api/users/test_user_upgrade_downgrade.js rename to test/mock-api/users/test_user_upgrade_downgrade_test.js diff --git a/test/mock-api/webhooks/common_test_constants.js b/test/mock-api/webhooks/common_test_constants.js new file mode 100644 index 0000000..8d36aee --- /dev/null +++ b/test/mock-api/webhooks/common_test_constants.js @@ -0,0 +1,118 @@ +// Common Webhook IDs +const TEST_WEBHOOK_ID = 4012345678901234; +const TEST_SCOPE_OBJECT_ID = 3285357287499652; + +// Common Webhook Properties +const TEST_WEBHOOK_NAME = 'Test Webhook'; +const TEST_CALLBACK_URL = 'https://www.myApp.com/webhooks'; +const TEST_SCOPE_SHEET = 'sheet'; +const TEST_SCOPE_PLAN = 'plan'; +const TEST_EVENTS = ['*.*']; +const TEST_VERSION = 1; +const TEST_ENABLED = false; +const TEST_STATUS = 'NEW_NOT_VERIFIED'; +const TEST_SHARED_SECRET = 'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz'; +const TEST_DISABLED_DETAILS = 'Webhook disabled due to verification failure'; +const TEST_API_CLIENT_ID = 'abc123client'; +const TEST_API_CLIENT_NAME = 'Test API Client'; + +// Common Webhook Timestamps +const TEST_CREATED_AT = '2020-08-25T12:15:47Z'; +const TEST_MODIFIED_AT = '2020-10-04T18:32:47Z'; +const TEST_LAST_CALLBACK_ATTEMPT = '2020-10-04T18:30:00Z'; +const TEST_LAST_SUCCESSFUL_CALLBACK = '2020-10-04T18:29:45Z'; + +// Common Webhook Stats +const TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT = 0; + +// Common Webhook Subscope (for sheet webhooks) +const TEST_COLUMN_IDS = [7960873114331012, 2234073893033860]; + +// Common Webhook Custom Headers (for plan webhooks) +const TEST_CUSTOM_HEADERS = { + 'x-custom-header': 'custom-value', + 'x-another-header': 'another-value' +}; + +// Common Request Bodies +const TEST_SHEET_WEBHOOK_REQUEST_BODY = { + name: TEST_WEBHOOK_NAME, + callbackUrl: TEST_CALLBACK_URL, + scope: TEST_SCOPE_SHEET, + scopeObjectId: TEST_SCOPE_OBJECT_ID, + events: TEST_EVENTS, + version: TEST_VERSION, + subscope: { + columnIds: TEST_COLUMN_IDS + } +}; + +const TEST_PLAN_WEBHOOK_REQUEST_BODY = { + name: TEST_WEBHOOK_NAME, + callbackUrl: TEST_CALLBACK_URL, + scope: TEST_SCOPE_PLAN, + scopeObjectId: TEST_SCOPE_OBJECT_ID, + events: TEST_EVENTS, + version: TEST_VERSION, + customHeaders: TEST_CUSTOM_HEADERS +}; + +const TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL = { + enabled: true +}; + +const TEST_UPDATE_SHEET_WEBHOOK_REQUEST_BODY = { + name: TEST_WEBHOOK_NAME, + enabled: true +}; + +const TEST_UPDATE_PLAN_WEBHOOK_REQUEST_BODY = { + name: TEST_WEBHOOK_NAME, + enabled: true, + customHeaders: TEST_CUSTOM_HEADERS +}; + +// Common Success Response Values +const TEST_SUCCESS_MESSAGE = 'SUCCESS'; +const TEST_SUCCESS_RESULT_CODE = 0; + +// Common Error Status Codes +const ERROR_500_STATUS_CODE = 500; +const ERROR_500_MESSAGE = 'Internal Server Error'; +const ERROR_400_STATUS_CODE = 400; +const ERROR_400_MESSAGE = 'Malformed Request'; + +module.exports = { + TEST_WEBHOOK_ID, + TEST_SCOPE_OBJECT_ID, + TEST_WEBHOOK_NAME, + TEST_CALLBACK_URL, + TEST_SCOPE_SHEET, + TEST_SCOPE_PLAN, + TEST_EVENTS, + TEST_VERSION, + TEST_ENABLED, + TEST_STATUS, + TEST_SHARED_SECRET, + TEST_DISABLED_DETAILS, + TEST_API_CLIENT_ID, + TEST_API_CLIENT_NAME, + TEST_CREATED_AT, + TEST_MODIFIED_AT, + TEST_LAST_CALLBACK_ATTEMPT, + TEST_LAST_SUCCESSFUL_CALLBACK, + TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT, + TEST_COLUMN_IDS, + TEST_CUSTOM_HEADERS, + TEST_SHEET_WEBHOOK_REQUEST_BODY, + TEST_PLAN_WEBHOOK_REQUEST_BODY, + TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + TEST_UPDATE_SHEET_WEBHOOK_REQUEST_BODY, + TEST_UPDATE_PLAN_WEBHOOK_REQUEST_BODY, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +}; diff --git a/test/mock-api/webhooks/test_create_webhook_test.js b/test/mock-api/webhooks/test_create_webhook_test.js new file mode 100644 index 0000000..6b325cb --- /dev/null +++ b/test/mock-api/webhooks/test_create_webhook_test.js @@ -0,0 +1,173 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_SCOPE_OBJECT_ID, + TEST_WEBHOOK_NAME, + TEST_CALLBACK_URL, + TEST_SCOPE_SHEET, + TEST_SCOPE_PLAN, + TEST_EVENTS, + TEST_VERSION, + TEST_ENABLED, + TEST_STATUS, + TEST_SHARED_SECRET, + TEST_DISABLED_DETAILS, + TEST_API_CLIENT_ID, + TEST_API_CLIENT_NAME, + TEST_CREATED_AT, + TEST_MODIFIED_AT, + TEST_LAST_CALLBACK_ATTEMPT, + TEST_LAST_SUCCESSFUL_CALLBACK, + TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT, + TEST_COLUMN_IDS, + TEST_CUSTOM_HEADERS, + TEST_SHEET_WEBHOOK_REQUEST_BODY, + TEST_PLAN_WEBHOOK_REQUEST_BODY, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - createWebhook endpoint tests', function () { + let client = createClient(); + + it('createWebhook generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + body: TEST_SHEET_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/create-sheet-webhook/all-response-body-properties' + } + }; + await client.webhooks.createWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(matchedRequest.url.includes('/2.0/webhooks')); + }); + + it('createWebhook sheet webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + body: TEST_SHEET_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/create-sheet-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.createWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.strictEqual(response.result.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.result.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.result.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.result.scope, TEST_SCOPE_SHEET); + assert.strictEqual(response.result.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.result.events, TEST_EVENTS); + assert.strictEqual(response.result.version, TEST_VERSION); + assert.ok(response.result.subscope); + assert.deepStrictEqual(response.result.subscope.columnIds, TEST_COLUMN_IDS); + assert.strictEqual(response.result.enabled, TEST_ENABLED); + assert.strictEqual(response.result.status, TEST_STATUS); + assert.strictEqual(response.result.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.result.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.result.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.result.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.result.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.result.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.result.stats); + assert.strictEqual(response.result.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.result.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.result.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + + let body = JSON.parse(matchedRequest.body); + assert.deepStrictEqual(body, TEST_SHEET_WEBHOOK_REQUEST_BODY); + }); + + it('createWebhook plan webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + body: TEST_PLAN_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/create-plan-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.createWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.strictEqual(response.result.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.result.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.result.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.result.scope, TEST_SCOPE_PLAN); + assert.strictEqual(response.result.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.result.events, TEST_EVENTS); + assert.strictEqual(response.result.version, TEST_VERSION); + assert.ok(response.result.customHeaders); + assert.deepStrictEqual(response.result.customHeaders, TEST_CUSTOM_HEADERS); + assert.strictEqual(response.result.enabled, TEST_ENABLED); + assert.strictEqual(response.result.status, TEST_STATUS); + assert.strictEqual(response.result.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.result.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.result.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.result.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.result.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.result.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.result.stats); + assert.strictEqual(response.result.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.result.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.result.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + + let body = JSON.parse(matchedRequest.body); + assert.deepStrictEqual(body, TEST_PLAN_WEBHOOK_REQUEST_BODY); + }); + + it('createWebhook error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + body: TEST_SHEET_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.createWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('createWebhook error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + body: TEST_SHEET_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.createWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +}); diff --git a/test/mock-api/webhooks/test_delete_webhook_test.js b/test/mock-api/webhooks/test_delete_webhook_test.js new file mode 100644 index 0000000..34f60f3 --- /dev/null +++ b/test/mock-api/webhooks/test_delete_webhook_test.js @@ -0,0 +1,86 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + TEST_VERSION, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - deleteWebhook endpoint tests', function () { + let client = createClient(); + + it('deleteWebhook generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/delete-webhook/all-response-body-properties' + } + }; + await client.webhooks.deleteWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(matchedRequest.url.includes(`/webhooks/${TEST_WEBHOOK_ID}`)); + }); + + it('deleteWebhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/delete-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.deleteWebhook(options); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.ok(response.failedItems); + }); + + it('deleteWebhook error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.deleteWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('deleteWebhook error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.deleteWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +}); diff --git a/test/mock-api/webhooks/test_get_webhook_test.js b/test/mock-api/webhooks/test_get_webhook_test.js new file mode 100644 index 0000000..51926a4 --- /dev/null +++ b/test/mock-api/webhooks/test_get_webhook_test.js @@ -0,0 +1,187 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_WEBHOOK_NAME, + TEST_CALLBACK_URL, + TEST_SCOPE_SHEET, + TEST_SCOPE_PLAN, + TEST_SCOPE_OBJECT_ID, + TEST_EVENTS, + TEST_VERSION, + TEST_ENABLED, + TEST_STATUS, + TEST_SHARED_SECRET, + TEST_CREATED_AT, + TEST_MODIFIED_AT, + TEST_DISABLED_DETAILS, + TEST_API_CLIENT_ID, + TEST_API_CLIENT_NAME, + TEST_LAST_CALLBACK_ATTEMPT, + TEST_LAST_SUCCESSFUL_CALLBACK, + TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT, + TEST_COLUMN_IDS, + TEST_CUSTOM_HEADERS, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - getWebhook endpoint tests', function () { + let client = createClient(); + + it('getWebhook generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/get-sheet-webhook/all-response-body-properties' + } + }; + await client.webhooks.getWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(matchedRequest.url.includes(`/webhooks/${TEST_WEBHOOK_ID}`)); + }); + + it('getWebhook sheet webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/get-sheet-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.getWebhook(options); + + assert.ok(response); + assert.strictEqual(response.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.scope, TEST_SCOPE_SHEET); + assert.strictEqual(response.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.events, TEST_EVENTS); + assert.strictEqual(response.version, TEST_VERSION); + assert.ok(response.subscope); + assert.deepStrictEqual(response.subscope.columnIds, TEST_COLUMN_IDS); + assert.strictEqual(response.enabled, TEST_ENABLED); + assert.strictEqual(response.status, TEST_STATUS); + assert.strictEqual(response.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.stats); + assert.strictEqual(response.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + }); + + it('getWebhook plan webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/get-plan-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.getWebhook(options); + + assert.ok(response); + assert.strictEqual(response.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.scope, TEST_SCOPE_PLAN); + assert.strictEqual(response.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.events, TEST_EVENTS); + assert.strictEqual(response.version, TEST_VERSION); + assert.ok(response.customHeaders); + assert.deepStrictEqual(response.customHeaders, TEST_CUSTOM_HEADERS); + assert.strictEqual(response.enabled, TEST_ENABLED); + assert.strictEqual(response.status, TEST_STATUS); + assert.strictEqual(response.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.stats); + assert.strictEqual(response.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + }); + + it('getWebhook required response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/get-webhook/required-response-body-properties' + } + }; + const response = await client.webhooks.getWebhook(options); + + assert.ok(response); + assert.strictEqual(response.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.scope, TEST_SCOPE_SHEET); + assert.strictEqual(response.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.events, TEST_EVENTS); + assert.strictEqual(response.version, TEST_VERSION); + assert.strictEqual(response.id, undefined); + assert.strictEqual(response.subscope, undefined); + assert.strictEqual(response.customHeaders, undefined); + assert.strictEqual(response.enabled, undefined); + assert.strictEqual(response.status, undefined); + assert.strictEqual(response.sharedSecret, undefined); + assert.strictEqual(response.createdAt, undefined); + assert.strictEqual(response.modifiedAt, undefined); + assert.strictEqual(response.disabledDetails, undefined); + assert.strictEqual(response.apiClientId, undefined); + assert.strictEqual(response.apiClientName, undefined); + assert.strictEqual(response.stats, undefined); + }); + + it('getWebhook error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.getWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('getWebhook error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.getWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +}); diff --git a/test/mock-api/webhooks/test_list_webhooks_test.js b/test/mock-api/webhooks/test_list_webhooks_test.js new file mode 100644 index 0000000..34161dd --- /dev/null +++ b/test/mock-api/webhooks/test_list_webhooks_test.js @@ -0,0 +1,192 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_SCOPE_OBJECT_ID, + TEST_WEBHOOK_NAME, + TEST_CALLBACK_URL, + TEST_SCOPE_SHEET, + TEST_SCOPE_PLAN, + TEST_EVENTS, + TEST_VERSION, + TEST_ENABLED, + TEST_STATUS, + TEST_SHARED_SECRET, + TEST_DISABLED_DETAILS, + TEST_API_CLIENT_ID, + TEST_API_CLIENT_NAME, + TEST_CREATED_AT, + TEST_MODIFIED_AT, + TEST_LAST_CALLBACK_ATTEMPT, + TEST_LAST_SUCCESSFUL_CALLBACK, + TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT, + TEST_COLUMN_IDS, + TEST_CUSTOM_HEADERS, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - listWebhooks endpoint tests', function () { + let client = createClient(); + const pageNumber = 1; + const pageSize = 100; + const totalPages = 1; + const totalCount = 2; + const includeAll = false; + + it('listWebhooks generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + queryParameters: { + includeAll: includeAll, + page: pageNumber, + pageSize: pageSize + }, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/list-webhooks/all-response-body-properties' + } + }; + await client.webhooks.listWebhooks(options); + const matchedRequest = await findWireMockRequest(requestId); + const queryParams = matchedRequest.queryParams; + const includeAllActual = queryParams.includeAll.values[0]; + const pageActual = queryParams.page.values[0]; + const pageSizeActual = queryParams.pageSize.values[0]; + + assert.ok(matchedRequest.url.includes('/2.0/webhooks')); + assert.strictEqual(includeAllActual, includeAll.toString()); + assert.strictEqual(parseInt(pageActual), pageNumber); + assert.strictEqual(parseInt(pageSizeActual), pageSize); + }); + + it('listWebhooks all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/list-webhooks/all-response-body-properties' + } + }; + const response = await client.webhooks.listWebhooks(options); + + assert.ok(response); + assert.strictEqual(response.pageNumber, pageNumber); + assert.strictEqual(response.pageSize, pageSize); + assert.strictEqual(response.totalPages, totalPages); + assert.strictEqual(response.totalCount, totalCount); + assert.ok(Array.isArray(response.data)); + assert.strictEqual(response.data.length, 2); + + // Verify first webhook (sheet webhook with all properties) + const sheetWebhook = response.data[0]; + assert.strictEqual(sheetWebhook.id, TEST_WEBHOOK_ID); + assert.strictEqual(sheetWebhook.name, TEST_WEBHOOK_NAME); + assert.strictEqual(sheetWebhook.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(sheetWebhook.scope, TEST_SCOPE_SHEET); + assert.strictEqual(sheetWebhook.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(sheetWebhook.events, TEST_EVENTS); + assert.strictEqual(sheetWebhook.version, TEST_VERSION); + assert.ok(sheetWebhook.subscope); + assert.deepStrictEqual(sheetWebhook.subscope.columnIds, TEST_COLUMN_IDS); + assert.strictEqual(sheetWebhook.enabled, TEST_ENABLED); + assert.strictEqual(sheetWebhook.status, TEST_STATUS); + assert.strictEqual(sheetWebhook.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(sheetWebhook.createdAt, TEST_CREATED_AT); + assert.strictEqual(sheetWebhook.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(sheetWebhook.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(sheetWebhook.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(sheetWebhook.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(sheetWebhook.stats); + assert.strictEqual(sheetWebhook.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(sheetWebhook.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(sheetWebhook.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + + // Verify second webhook (plan webhook with custom headers) + const planWebhook = response.data[1]; + assert.strictEqual(planWebhook.id, TEST_WEBHOOK_ID + 1); + assert.strictEqual(planWebhook.name, 'Test Plan Webhook'); + assert.strictEqual(planWebhook.scope, TEST_SCOPE_PLAN); + assert.ok(planWebhook.customHeaders); + assert.deepStrictEqual(planWebhook.customHeaders, TEST_CUSTOM_HEADERS); + }); + + it('listWebhooks required response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/list-webhooks/required-response-body-properties' + } + }; + const response = await client.webhooks.listWebhooks(options); + + assert.ok(response); + assert.strictEqual(response.pageNumber, pageNumber); + assert.strictEqual(response.pageSize, pageSize); + assert.strictEqual(response.totalPages, totalPages); + assert.strictEqual(response.totalCount, 1); + assert.ok(Array.isArray(response.data)); + assert.strictEqual(response.data.length, 1); + + // Verify webhook has only required properties + const webhook = response.data[0]; + assert.strictEqual(webhook.id, TEST_WEBHOOK_ID); + assert.strictEqual(webhook.name, TEST_WEBHOOK_NAME); + assert.strictEqual(webhook.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(webhook.scope, TEST_SCOPE_SHEET); + assert.strictEqual(webhook.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(webhook.events, TEST_EVENTS); + assert.strictEqual(webhook.version, TEST_VERSION); + + // Verify optional properties are not present + assert.strictEqual(webhook.subscope, undefined); + assert.strictEqual(webhook.enabled, undefined); + assert.strictEqual(webhook.status, undefined); + assert.strictEqual(webhook.sharedSecret, undefined); + assert.strictEqual(webhook.createdAt, undefined); + assert.strictEqual(webhook.modifiedAt, undefined); + assert.strictEqual(webhook.disabledDetails, undefined); + assert.strictEqual(webhook.apiClientId, undefined); + assert.strictEqual(webhook.apiClientName, undefined); + assert.strictEqual(webhook.stats, undefined); + assert.strictEqual(webhook.customHeaders, undefined); + }); + + it('listWebhooks error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.listWebhooks(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('listWebhooks error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.listWebhooks(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +}); diff --git a/test/mock-api/webhooks/test_reset_shared_secret_test.js b/test/mock-api/webhooks/test_reset_shared_secret_test.js new file mode 100644 index 0000000..b04d8f6 --- /dev/null +++ b/test/mock-api/webhooks/test_reset_shared_secret_test.js @@ -0,0 +1,89 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + TEST_VERSION, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - resetSharedSecret endpoint tests', function () { + let client = createClient(); + const newSharedSecret = 'new123secret456value789abc012def345ghi678jkl901mno234pqr'; + + it('resetSharedSecret generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/reset-shared-secret/all-response-body-properties' + } + }; + await client.webhooks.resetSharedSecret(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(matchedRequest.url.includes(`/webhooks/${TEST_WEBHOOK_ID}/resetsharedsecret`)); + }); + + it('resetSharedSecret all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/reset-shared-secret/all-response-body-properties' + } + }; + const response = await client.webhooks.resetSharedSecret(options); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.ok(response.failedItems); + assert.ok(response.result); + assert.strictEqual(response.result.sharedSecret, newSharedSecret); + }); + + it('resetSharedSecret error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.resetSharedSecret(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('resetSharedSecret error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.resetSharedSecret(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +}); diff --git a/test/mock-api/webhooks/test_update_webhook_test.js b/test/mock-api/webhooks/test_update_webhook_test.js new file mode 100644 index 0000000..a496d9f --- /dev/null +++ b/test/mock-api/webhooks/test_update_webhook_test.js @@ -0,0 +1,214 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { createClient, findWireMockRequest } = require('../utils/utils.js'); +const { + TEST_WEBHOOK_ID, + TEST_WEBHOOK_NAME, + TEST_CALLBACK_URL, + TEST_SCOPE_SHEET, + TEST_SCOPE_PLAN, + TEST_SCOPE_OBJECT_ID, + TEST_EVENTS, + TEST_VERSION, + TEST_ENABLED, + TEST_STATUS, + TEST_SHARED_SECRET, + TEST_CREATED_AT, + TEST_MODIFIED_AT, + TEST_DISABLED_DETAILS, + TEST_API_CLIENT_ID, + TEST_API_CLIENT_NAME, + TEST_LAST_CALLBACK_ATTEMPT, + TEST_LAST_SUCCESSFUL_CALLBACK, + TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT, + TEST_COLUMN_IDS, + TEST_CUSTOM_HEADERS, + TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + TEST_UPDATE_SHEET_WEBHOOK_REQUEST_BODY, + TEST_UPDATE_PLAN_WEBHOOK_REQUEST_BODY, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} = require('./common_test_constants.js'); + +describe('Webhooks - updateWebhook endpoint tests', function () { + let client = createClient(); + + it('updateWebhook generated url is correct', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/update-sheet-webhook/all-response-body-properties' + } + }; + await client.webhooks.updateWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(matchedRequest.url.includes(`/webhooks/${TEST_WEBHOOK_ID}`)); + }); + + it('updateWebhook sheet webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_SHEET_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/update-sheet-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.updateWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.strictEqual(response.result.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.result.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.result.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.result.scope, TEST_SCOPE_SHEET); + assert.strictEqual(response.result.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.result.events, TEST_EVENTS); + assert.strictEqual(response.result.version, TEST_VERSION); + assert.ok(response.result.subscope); + assert.deepStrictEqual(response.result.subscope.columnIds, TEST_COLUMN_IDS); + assert.strictEqual(response.result.enabled, TEST_ENABLED); + assert.strictEqual(response.result.status, TEST_STATUS); + assert.strictEqual(response.result.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.result.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.result.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.result.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.result.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.result.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.result.stats); + assert.strictEqual(response.result.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.result.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.result.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + + let body = JSON.parse(matchedRequest.body); + assert.deepStrictEqual(body, TEST_UPDATE_SHEET_WEBHOOK_REQUEST_BODY); + }); + + it('updateWebhook plan webhook all response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_PLAN_WEBHOOK_REQUEST_BODY, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/update-plan-webhook/all-response-body-properties' + } + }; + const response = await client.webhooks.updateWebhook(options); + const matchedRequest = await findWireMockRequest(requestId); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.version, TEST_VERSION); + assert.strictEqual(response.result.id, TEST_WEBHOOK_ID); + assert.strictEqual(response.result.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.result.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.result.scope, TEST_SCOPE_PLAN); + assert.strictEqual(response.result.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.result.events, TEST_EVENTS); + assert.strictEqual(response.result.version, TEST_VERSION); + assert.ok(response.result.customHeaders); + assert.deepStrictEqual(response.result.customHeaders, TEST_CUSTOM_HEADERS); + assert.strictEqual(response.result.enabled, TEST_ENABLED); + assert.strictEqual(response.result.status, TEST_STATUS); + assert.strictEqual(response.result.sharedSecret, TEST_SHARED_SECRET); + assert.strictEqual(response.result.createdAt, TEST_CREATED_AT); + assert.strictEqual(response.result.modifiedAt, TEST_MODIFIED_AT); + assert.strictEqual(response.result.disabledDetails, TEST_DISABLED_DETAILS); + assert.strictEqual(response.result.apiClientId, TEST_API_CLIENT_ID); + assert.strictEqual(response.result.apiClientName, TEST_API_CLIENT_NAME); + assert.ok(response.result.stats); + assert.strictEqual(response.result.stats.lastCallbackAttempt, TEST_LAST_CALLBACK_ATTEMPT); + assert.strictEqual(response.result.stats.lastCallbackAttemptRetryCount, TEST_LAST_CALLBACK_ATTEMPT_RETRY_COUNT); + assert.strictEqual(response.result.stats.lastSuccessfulCallback, TEST_LAST_SUCCESSFUL_CALLBACK); + + let body = JSON.parse(matchedRequest.body); + assert.deepStrictEqual(body, TEST_UPDATE_PLAN_WEBHOOK_REQUEST_BODY); + }); + + it('updateWebhook required response body properties', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/webhooks/update-webhook/required-response-body-properties' + } + }; + const response = await client.webhooks.updateWebhook(options); + + assert.ok(response); + assert.strictEqual(response.message, TEST_SUCCESS_MESSAGE); + assert.strictEqual(response.resultCode, TEST_SUCCESS_RESULT_CODE); + assert.strictEqual(response.result.name, TEST_WEBHOOK_NAME); + assert.strictEqual(response.result.callbackUrl, TEST_CALLBACK_URL); + assert.strictEqual(response.result.scope, TEST_SCOPE_SHEET); + assert.strictEqual(response.result.scopeObjectId, TEST_SCOPE_OBJECT_ID); + assert.deepStrictEqual(response.result.events, TEST_EVENTS); + assert.strictEqual(response.result.version, TEST_VERSION); + assert.strictEqual(response.result.id, undefined); + assert.strictEqual(response.result.subscope, undefined); + assert.strictEqual(response.result.customHeaders, undefined); + assert.strictEqual(response.result.enabled, undefined); + assert.strictEqual(response.result.status, undefined); + assert.strictEqual(response.result.sharedSecret, undefined); + assert.strictEqual(response.result.createdAt, undefined); + assert.strictEqual(response.result.modifiedAt, undefined); + assert.strictEqual(response.result.disabledDetails, undefined); + assert.strictEqual(response.result.apiClientId, undefined); + assert.strictEqual(response.result.apiClientName, undefined); + assert.strictEqual(response.result.stats, undefined); + }); + + it('updateWebhook error 500 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.webhooks.updateWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_500_STATUS_CODE); + assert.strictEqual(error.message, ERROR_500_MESSAGE); + } + }); + + it('updateWebhook error 400 response', async function () { + const requestId = crypto.randomUUID(); + const options = { + webhookId: TEST_WEBHOOK_ID, + body: TEST_UPDATE_WEBHOOK_REQUEST_BODY_MINIMAL, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.webhooks.updateWebhook(options); + assert.fail('Expected an error to be thrown'); + } catch (error) { + assert.strictEqual(error.statusCode, ERROR_400_STATUS_CODE); + assert.strictEqual(error.message, ERROR_400_MESSAGE); + } + }); +});