diff --git a/src/models/notification/notifications.models.ts b/src/models/notification/notifications.models.ts index da7f109bb..f61a551ad 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,38 @@ 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('', ['', '']); + * ``` + * @internal + */ + 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(''); + * ``` + * @internal + */ + deleteAll(tenantId: string): Promise; } diff --git a/src/services/notification/notifications.ts b/src/services/notification/notifications.ts index f53ab6d00..143decf78 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,56 @@ 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('', ['', '']); + * ``` + * @internal + */ + @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(''); + * ``` + * @internal + */ + @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();