From 577dc47c941eaaa9f11635e8c62bc0a4b99bda53 Mon Sep 17 00:00:00 2001 From: sarthak688 <107241313+sarthak688@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:59:08 +0530 Subject: [PATCH 1/2] feat(notifications): add delete flows (deleteNotifications, deleteAll) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the two delete methods. Both POST to the NotificationEntry.DeleteBulk OData action; deleteAll uses the server-side deleteAll flag. Preserves the API spec's misspelling `notifcationIds` in the request body — the server expects that exact (mistyped) key. Tests: 4 additional unit tests. No integration tests — these destructively mutate the inbox with no SDK-level undo. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oauth-scopes.md | 2 + .../notification/notifications.models.ts | 48 ++++++++++++++++++ src/services/notification/notifications.ts | 50 +++++++++++++++++++ src/utils/constants/endpoints/notification.ts | 1 + .../notifications.integration.test.ts | 3 ++ .../notification/notifications.test.ts | 50 +++++++++++++++++++ 6 files changed, 154 insertions(+) diff --git a/docs/oauth-scopes.md b/docs/oauth-scopes.md index 24495b0b7..d34215e48 100644 --- a/docs/oauth-scopes.md +++ b/docs/oauth-scopes.md @@ -217,6 +217,8 @@ The `ConversationalAgents` scope is required for real-time WebSocket sessions (` | `markRead()` | `NotificationService` | | `markUnread()` | `NotificationService` | | `markAllRead()` | `NotificationService` | +| `deleteNotifications()` | `NotificationService` | +| `deleteAll()` | `NotificationService` | ## Processes diff --git a/src/models/notification/notifications.models.ts b/src/models/notification/notifications.models.ts index da7f109bb..ba22fe136 100644 --- a/src/models/notification/notifications.models.ts +++ b/src/models/notification/notifications.models.ts @@ -33,6 +33,22 @@ export type NotificationMarkAllReadResponse = OperationResponse<{ read: true; }>; +/** + * Response from `deleteNotifications()`. + * + * `notificationIds` echoes the IDs that were deleted. + */ +export type NotificationDeleteResponse = OperationResponse<{ + notificationIds: string[]; +}>; + +/** + * Response from `deleteAll()`. + */ +export type NotificationDeleteAllResponse = OperationResponse<{ + all: true; +}>; + /** * Public surface of the Notifications service. JSDoc on this interface drives * the generated API reference documentation. @@ -132,4 +148,36 @@ export interface NotificationServiceModel { * ``` */ markAllRead(tenantId: string): Promise; + + /** + * Deletes the given notifications. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param notificationIds - GUIDs of notifications to delete. Must be non-empty — + * the server rejects an empty array with HTTP 400. + * @returns Operation result echoing the deleted IDs + * {@link NotificationDeleteResponse} + * + * @example + * ```typescript + * await notifications.deleteNotifications('', ['', '']); + * ``` + */ + deleteNotifications(tenantId: string, notificationIds: string[]): Promise; + + /** + * Deletes all notifications from the current user's inbox. + * + * Uses the server-side `deleteAll` flag — no per-notification IDs are sent. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @returns Operation result confirming the bulk delete + * {@link NotificationDeleteAllResponse} + * + * @example + * ```typescript + * await notifications.deleteAll(''); + * ``` + */ + deleteAll(tenantId: string): Promise; } diff --git a/src/services/notification/notifications.ts b/src/services/notification/notifications.ts index f53ab6d00..60e0201d0 100644 --- a/src/services/notification/notifications.ts +++ b/src/services/notification/notifications.ts @@ -10,6 +10,8 @@ import type { NotificationGetResponse, } from '../../models/notification/notifications.types'; import type { + NotificationDeleteAllResponse, + NotificationDeleteResponse, NotificationMarkAllReadResponse, NotificationServiceModel, NotificationUpdateReadResponse, @@ -169,6 +171,54 @@ export class NotificationService extends BaseService implements NotificationServ return { success: true, data: { all: true, read: true } }; } + /** + * Deletes the given notifications. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param notificationIds - GUIDs of notifications to delete. Must be non-empty — + * the server rejects an empty array with HTTP 400. + * @returns Operation result echoing the deleted IDs + * {@link NotificationDeleteResponse} + * + * @example + * ```typescript + * await notifications.deleteNotifications('', ['', '']); + * ``` + */ + @track('Notifications.DeleteNotifications') + async deleteNotifications(tenantId: string, notificationIds: string[]): Promise { + await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, { + // API spec misspells the key as `notifcationIds` — preserve it. + notifcationIds: notificationIds, + deleteAll: false, + }, { headers: createHeaders({ [TENANT_ID]: tenantId }) }); + return { success: true, data: { notificationIds } }; + } + + /** + * Deletes all notifications from the current user's inbox. + * + * Uses the server-side `deleteAll` flag — no per-notification IDs are sent. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @returns Operation result confirming the bulk delete + * {@link NotificationDeleteAllResponse} + * + * @example + * ```typescript + * await notifications.deleteAll(''); + * ``` + */ + @track('Notifications.DeleteAll') + async deleteAll(tenantId: string): Promise { + await this.post(NOTIFICATION_ENDPOINTS.DELETE_BULK, { + // API spec misspells the key as `notifcationIds` — preserve it. + notifcationIds: [], + deleteAll: true, + }, { headers: createHeaders({ [TENANT_ID]: tenantId }) }); + return { success: true, data: { all: true } }; + } + private async updateRead( tenantId: string, notificationIds: string[], diff --git a/src/utils/constants/endpoints/notification.ts b/src/utils/constants/endpoints/notification.ts index ab5c00a39..211bee2c6 100644 --- a/src/utils/constants/endpoints/notification.ts +++ b/src/utils/constants/endpoints/notification.ts @@ -16,4 +16,5 @@ const NOTIFICATION_API_BASE = `${NOTIFICATION_BASE}/notificationserviceapi`; export const NOTIFICATION_ENDPOINTS = { GET_ALL: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry`, UPDATE_READ: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.UpdateRead`, + DELETE_BULK: `${NOTIFICATION_API_BASE}/odata/v1/NotificationEntry/UiPath.NotificationService.Api.DeleteBulk`, } as const; diff --git a/tests/integration/shared/notification/notifications.integration.test.ts b/tests/integration/shared/notification/notifications.integration.test.ts index c9099f92c..5a5c6b69a 100644 --- a/tests/integration/shared/notification/notifications.integration.test.ts +++ b/tests/integration/shared/notification/notifications.integration.test.ts @@ -109,4 +109,7 @@ describe.each(modes)('Notifications - Integration Tests [%s]', (mode) => { expect(result.data).toEqual({ all: true, read: true }); }); }); + + // Note: no deleteNotifications / deleteAll integration tests — these destructively + // mutate the inbox with no SDK-level undo. Run manually if needed. }); diff --git a/tests/unit/services/notification/notifications.test.ts b/tests/unit/services/notification/notifications.test.ts index 314960eb9..6efc230ee 100644 --- a/tests/unit/services/notification/notifications.test.ts +++ b/tests/unit/services/notification/notifications.test.ts @@ -186,6 +186,56 @@ describe('NotificationService Unit Tests', () => { }); }); + describe('deleteNotifications', () => { + it('should POST notifcationIds (preserving the API typo), deleteAll=false, and tenant header', async () => { + mockApiClient.post.mockResolvedValue(undefined); + const ids = [ + NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID, + NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID_2, + ]; + + const result = await notificationService.deleteNotifications(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, ids); + + expect(mockApiClient.post).toHaveBeenCalledWith( + NOTIFICATION_ENDPOINTS.DELETE_BULK, + { notifcationIds: ids, deleteAll: false }, + { headers: TENANT_HEADER } + ); + expect(result).toEqual({ success: true, data: { notificationIds: ids } }); + }); + + it('should propagate errors', async () => { + mockApiClient.post.mockRejectedValue(createMockError(TEST_CONSTANTS.ERROR_MESSAGE)); + + await expect( + notificationService.deleteNotifications(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, [NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID]) + ).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE); + }); + }); + + describe('deleteAll', () => { + it('should POST deleteAll=true with empty notifcationIds array and tenant header', async () => { + mockApiClient.post.mockResolvedValue(undefined); + + const result = await notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID); + + expect(mockApiClient.post).toHaveBeenCalledWith( + NOTIFICATION_ENDPOINTS.DELETE_BULK, + { notifcationIds: [], deleteAll: true }, + { headers: TENANT_HEADER } + ); + expect(result).toEqual({ success: true, data: { all: true } }); + }); + + it('should propagate errors', async () => { + mockApiClient.post.mockRejectedValue(createMockError(TEST_CONSTANTS.ERROR_MESSAGE)); + + await expect( + notificationService.deleteAll(NOTIFICATION_TEST_CONSTANTS.TENANT_ID) + ).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE); + }); + }); + describe('stripInternalNotificationFields', () => { it('removes all 8 internal fields without mutating the original', () => { const raw: RawNotificationEntry = createBasicNotificationEntry(); From a489927ba5502cfd6cc9e16b34776853442989ba Mon Sep 17 00:00:00 2001 From: sarthak688 <107241313+sarthak688@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:31:31 +0530 Subject: [PATCH 2/2] fix(notifications): tag delete methods @internal + remove oauth-scopes entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same treatment as PR #512 / #513 — tag deleteNotifications, deleteAll @internal in models + service, drop their oauth-scopes entries. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oauth-scopes.md | 2 -- src/models/notification/notifications.models.ts | 2 ++ src/services/notification/notifications.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/oauth-scopes.md b/docs/oauth-scopes.md index d34215e48..24495b0b7 100644 --- a/docs/oauth-scopes.md +++ b/docs/oauth-scopes.md @@ -217,8 +217,6 @@ The `ConversationalAgents` scope is required for real-time WebSocket sessions (` | `markRead()` | `NotificationService` | | `markUnread()` | `NotificationService` | | `markAllRead()` | `NotificationService` | -| `deleteNotifications()` | `NotificationService` | -| `deleteAll()` | `NotificationService` | ## Processes diff --git a/src/models/notification/notifications.models.ts b/src/models/notification/notifications.models.ts index ba22fe136..f61a551ad 100644 --- a/src/models/notification/notifications.models.ts +++ b/src/models/notification/notifications.models.ts @@ -162,6 +162,7 @@ export interface NotificationServiceModel { * ```typescript * await notifications.deleteNotifications('', ['', '']); * ``` + * @internal */ deleteNotifications(tenantId: string, notificationIds: string[]): Promise; @@ -178,6 +179,7 @@ export interface NotificationServiceModel { * ```typescript * await notifications.deleteAll(''); * ``` + * @internal */ deleteAll(tenantId: string): Promise; } diff --git a/src/services/notification/notifications.ts b/src/services/notification/notifications.ts index 60e0201d0..143decf78 100644 --- a/src/services/notification/notifications.ts +++ b/src/services/notification/notifications.ts @@ -184,6 +184,7 @@ export class NotificationService extends BaseService implements NotificationServ * ```typescript * await notifications.deleteNotifications('', ['', '']); * ``` + * @internal */ @track('Notifications.DeleteNotifications') async deleteNotifications(tenantId: string, notificationIds: string[]): Promise { @@ -208,6 +209,7 @@ export class NotificationService extends BaseService implements NotificationServ * ```typescript * await notifications.deleteAll(''); * ``` + * @internal */ @track('Notifications.DeleteAll') async deleteAll(tenantId: string): Promise {