diff --git a/src/models/notification/notifications.models.ts b/src/models/notification/notifications.models.ts index f97764818..1720ab3e3 100644 --- a/src/models/notification/notifications.models.ts +++ b/src/models/notification/notifications.models.ts @@ -10,6 +10,8 @@ import type { } from '../../utils/pagination/types'; import type { + NotificationDeleteAllResponse, + NotificationDeleteResponse, NotificationGetAllOptions, NotificationGetResponse, NotificationMarkAllReadResponse, @@ -116,4 +118,35 @@ export interface NotificationServiceModel { * @internal */ markAllAsRead(tenantId: string): Promise; + + /** + * Deletes the given notifications. + * + * @param tenantId - Tenant GUID + * @param notificationIds - GUIDs of notifications to delete. Must be non-empty. + * @returns Operation result echoing the deleted IDs + * {@link NotificationDeleteResponse} + * + * @example + * ```typescript + * await notifications.deleteByIds('', ['', '']); + * ``` + * @internal + */ + deleteByIds(tenantId: string, notificationIds: string[]): Promise; + + /** + * Deletes all notifications from the current user's inbox. + * + * @param tenantId - Tenant GUID + * @returns Operation result confirming the bulk delete + * {@link NotificationDeleteAllResponse} + * + * @example + * ```typescript + * await notifications.deleteAll(''); + * ``` + * @internal + */ + deleteAll(tenantId: string): Promise; } diff --git a/src/models/notification/notifications.types.ts b/src/models/notification/notifications.types.ts index 9d3291632..ebdde50d1 100644 --- a/src/models/notification/notifications.types.ts +++ b/src/models/notification/notifications.types.ts @@ -105,3 +105,19 @@ export type NotificationMarkAllReadResponse = OperationResponse<{ all: true; read: true; }>; + +/** + * Response from `deleteByIds()`. + * + * `notificationIds` echoes the IDs that were deleted. + */ +export type NotificationDeleteResponse = OperationResponse<{ + notificationIds: string[]; +}>; + +/** + * Response from `deleteAll()`. + */ +export type NotificationDeleteAllResponse = OperationResponse<{ + all: true; +}>; diff --git a/src/services/notification/notifications.ts b/src/services/notification/notifications.ts index c87d8e9e4..0e04a2074 100644 --- a/src/services/notification/notifications.ts +++ b/src/services/notification/notifications.ts @@ -6,6 +6,8 @@ import { track } from '../../core/telemetry'; import { BaseService } from '../base'; import type { + NotificationDeleteAllResponse, + NotificationDeleteResponse, NotificationGetAllOptions, NotificationGetResponse, NotificationMarkAllReadResponse, @@ -187,6 +189,53 @@ export class NotificationService extends BaseService implements NotificationServ return { success: true, data: { all: true, read: true } }; } + /** + * Deletes the given notifications. + * + * @param tenantId - Tenant GUID + * @param notificationIds - GUIDs of notifications to delete. Must be non-empty. + * @returns Operation result echoing the deleted IDs + * {@link NotificationDeleteResponse} + * + * @example + * ```typescript + * await notifications.deleteByIds('', ['', '']); + * ``` + * @internal + */ + @track('Notifications.DeleteByIds') + async deleteByIds(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. + * + * @param tenantId - Tenant GUID + * @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/unit/services/notification/notifications.test.ts b/tests/unit/services/notification/notifications.test.ts index 4841cd00f..a86c7d17f 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('deleteByIds', () => { + 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.deleteByIds(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(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND)); + + await expect( + notificationService.deleteByIds(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, [NOTIFICATION_TEST_CONSTANTS.NOTIFICATION_ID]) + ).rejects.toThrow(NOTIFICATION_TEST_CONSTANTS.ERROR_NOTIFICATION_NOT_FOUND); + }); + }); + + 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('response transformation', () => { it('strips internal fields and renames isRead → hasRead in the returned notifications', async () => { const raw: RawNotificationEntry = createBasicNotificationEntry();