diff --git a/src/models/notification/subscriptions.models.ts b/src/models/notification/subscriptions.models.ts index cda86722b..59f218e7c 100644 --- a/src/models/notification/subscriptions.models.ts +++ b/src/models/notification/subscriptions.models.ts @@ -6,6 +6,7 @@ import type { OperationResponse } from '../common/types'; import type { + AllowedMode, CategorySubscriptionUpdate, PublisherSubscriptionUpdate, SubscriptionGetAllOptions, @@ -56,6 +57,12 @@ export type SubscriptionUpdateTopicGroupResponse = OperationResponse<{ subscriptions: TopicGroupSubscriptionUpdate[]; }>; +/** Response from `updateMode()`. */ +export type SubscriptionUpdateModeResponse = OperationResponse<{ + publisherId: string; + mode: AllowedMode; +}>; + /** * Public surface of the Subscriptions service. JSDoc on this interface drives * the generated API reference documentation. @@ -230,4 +237,44 @@ export interface SubscriptionServiceModel { * ``` */ updateTopicGroup(tenantId: string, subscriptions: TopicGroupSubscriptionUpdate[]): Promise; + + /** + * Activates or deactivates a notification channel for a publisher. + * + * Use {@link getSupportedChannels} first to check whether the channel is enabled in the tenant. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param publisherId - Publisher GUID + * @param mode - Channel and target activation state + * @returns Operation result echoing the new mode state + * {@link SubscriptionUpdateModeResponse} + * + * @example Activate email delivery for a publisher + * ```typescript + * import { NotificationMode } from '@uipath/uipath-typescript/notifications'; + * + * await subscriptions.updateMode('', '', { + * name: NotificationMode.Email, + * isActive: true, + * }); + * ``` + * @internal + */ + updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise; + + /** + * Resets the current user's subscriptions for a publisher to the publisher's defaults. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param publisherId - Publisher GUID + * @returns The publisher's full subscription state after reset + * {@link SubscriptionGetResponse} + * + * @example + * ```typescript + * const { publishers } = await subscriptions.reset('', ''); + * ``` + * @internal + */ + reset(tenantId: string, publisherId: string): Promise; } diff --git a/src/services/notification/subscriptions.ts b/src/services/notification/subscriptions.ts index 7c7be7546..43c10f2f9 100644 --- a/src/services/notification/subscriptions.ts +++ b/src/services/notification/subscriptions.ts @@ -6,6 +6,7 @@ import { track } from '../../core/telemetry'; import { BaseService } from '../base'; import type { + AllowedMode, CategorySubscriptionUpdate, PublisherSubscriptionUpdate, SubscriptionGetAllOptions, @@ -18,6 +19,7 @@ import type { SubscriptionGetSupportedChannelsResponse, SubscriptionServiceModel, SubscriptionUpdateCategoryResponse, + SubscriptionUpdateModeResponse, SubscriptionUpdatePublisherResponse, SubscriptionUpdateTopicGroupResponse, SubscriptionUpdateTopicResponse, @@ -263,4 +265,57 @@ export class SubscriptionService extends BaseService implements SubscriptionServ }, { headers: createHeaders({ [TENANT_ID]: tenantId }) }); return { success: true, data: { subscriptions } }; } + + /** + * Activates or deactivates a notification channel for a publisher. + * + * Use {@link getSupportedChannels} first to check whether the channel is enabled in the tenant. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param publisherId - Publisher GUID + * @param mode - Channel and target activation state + * @returns Operation result echoing the new mode state + * {@link SubscriptionUpdateModeResponse} + * + * @example Activate email delivery for a publisher + * ```typescript + * import { NotificationMode } from '@uipath/uipath-typescript/notifications'; + * + * await subscriptions.updateMode('', '', { + * name: NotificationMode.Email, + * isActive: true, + * }); + * ``` + * @internal + */ + @track('Subscriptions.UpdateMode') + async updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise { + await this.post(SUBSCRIPTION_ENDPOINTS.UPDATE_MODE, { + publisherId, + publisherMode: mode, + }, { headers: createHeaders({ [TENANT_ID]: tenantId }) }); + return { success: true, data: { publisherId, mode } }; + } + + /** + * Resets the current user's subscriptions for a publisher to the publisher's defaults. + * + * @param tenantId - Tenant GUID (sent via `X-UIPATH-Internal-TenantId`) + * @param publisherId - Publisher GUID + * @returns The publisher's full subscription state after reset + * {@link SubscriptionGetResponse} + * + * @example + * ```typescript + * const { publishers } = await subscriptions.reset('', ''); + * ``` + * @internal + */ + @track('Subscriptions.Reset') + async reset(tenantId: string, publisherId: string): Promise { + const response = await this.post(SUBSCRIPTION_ENDPOINTS.RESET, { + publisherId, + }, { headers: createHeaders({ [TENANT_ID]: tenantId }) }); + return response.data; + } } diff --git a/src/utils/constants/endpoints/notification.ts b/src/utils/constants/endpoints/notification.ts index 91adba4c4..5f93dc407 100644 --- a/src/utils/constants/endpoints/notification.ts +++ b/src/utils/constants/endpoints/notification.ts @@ -34,4 +34,6 @@ export const SUBSCRIPTION_ENDPOINTS = { UPDATE_CATEGORY: `${SUBSCRIPTION_API_BASE}/api/v1/UserSubscription/CategorySubscription`, UPDATE_PUBLISHER: `${SUBSCRIPTION_API_BASE}/api/v1/UserSubscription/PublisherSubscription`, UPDATE_TOPIC_GROUP: `${SUBSCRIPTION_API_BASE}/api/v1/UserSubscription/TopicGroupSubscription`, + UPDATE_MODE: `${SUBSCRIPTION_API_BASE}/api/v1/UserSubscription/UpdateMode`, + RESET: `${SUBSCRIPTION_API_BASE}/api/v1/UserSubscription/Reset`, } as const; diff --git a/tests/integration/shared/notification/subscriptions.integration.test.ts b/tests/integration/shared/notification/subscriptions.integration.test.ts index fbc5e3044..c6610f4fb 100644 --- a/tests/integration/shared/notification/subscriptions.integration.test.ts +++ b/tests/integration/shared/notification/subscriptions.integration.test.ts @@ -126,7 +126,17 @@ describe.each(modes)('Subscriptions - Integration Tests [%s]', (mode) => { }); }); - // Note: no updateCategory / updateTopicGroup integration tests — they need richer - // tenant fixtures (multi-topic category coverage, configured topic groups). Covered - // by unit tests for SDK shape. + describe('reset', () => { + it('should reset publisher subscriptions and return updated state', async () => { + const result = await subscriptions.reset(tenantId, firstPublisher.id); + + expect(Array.isArray(result.publishers)).toBe(true); + const resetPub = result.publishers.find((p) => p.id === firstPublisher.id); + expect(resetPub).toBeDefined(); + }); + }); + + // Note: no updateCategory / updateTopicGroup / updateMode integration tests — they + // need richer tenant fixtures (multi-topic category coverage, configured topic groups, + // active Slack/Teams channels). Covered by unit tests for SDK shape. }); diff --git a/tests/unit/services/notification/subscriptions.test.ts b/tests/unit/services/notification/subscriptions.test.ts index 14e3d5940..5a5825087 100644 --- a/tests/unit/services/notification/subscriptions.test.ts +++ b/tests/unit/services/notification/subscriptions.test.ts @@ -322,4 +322,86 @@ describe('SubscriptionService Unit Tests', () => { ).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE); }); }); + + describe('updateMode', () => { + it('should POST publisherId + publisherMode with tenant header and echo input', async () => { + mockApiClient.post.mockResolvedValue(undefined); + const mode = { name: NotificationMode.Email, isActive: true }; + + const result = await subscriptionService.updateMode( + NOTIFICATION_TEST_CONSTANTS.TENANT_ID, + NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, + mode + ); + + expect(mockApiClient.post).toHaveBeenCalledWith( + SUBSCRIPTION_ENDPOINTS.UPDATE_MODE, + { publisherId: NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, publisherMode: mode }, + { headers: TENANT_HEADER } + ); + expect(result).toEqual({ + success: true, + data: { publisherId: NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, mode }, + }); + }); + + it('should support deactivating a mode', async () => { + mockApiClient.post.mockResolvedValue(undefined); + const mode = { name: NotificationMode.Slack, isActive: false }; + + await subscriptionService.updateMode( + NOTIFICATION_TEST_CONSTANTS.TENANT_ID, + NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, + mode + ); + + expect(mockApiClient.post).toHaveBeenCalledWith( + SUBSCRIPTION_ENDPOINTS.UPDATE_MODE, + { publisherId: NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, publisherMode: mode }, + { headers: TENANT_HEADER } + ); + }); + + it('should propagate errors', async () => { + mockApiClient.post.mockRejectedValue(createMockError(TEST_CONSTANTS.ERROR_MESSAGE)); + + await expect( + subscriptionService.updateMode( + NOTIFICATION_TEST_CONSTANTS.TENANT_ID, + NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID, + { + name: NotificationMode.InApp, + isActive: true, + } + ) + ).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE); + }); + }); + + describe('reset', () => { + it('should POST publisherId with tenant header and return the publisher subscription state', async () => { + const mockData = { publishers: [createBasicSubscriptionPublisher()] }; + mockApiClient.post.mockResolvedValue(mockData); + + const result = await subscriptionService.reset( + NOTIFICATION_TEST_CONSTANTS.TENANT_ID, + NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID + ); + + expect(mockApiClient.post).toHaveBeenCalledWith( + SUBSCRIPTION_ENDPOINTS.RESET, + { publisherId: NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID }, + { headers: TENANT_HEADER } + ); + expect(result).toEqual(mockData); + }); + + it('should propagate errors', async () => { + mockApiClient.post.mockRejectedValue(createMockError(NOTIFICATION_TEST_CONSTANTS.ERROR_PUBLISHER_NOT_FOUND)); + + await expect( + subscriptionService.reset(NOTIFICATION_TEST_CONSTANTS.TENANT_ID, NOTIFICATION_TEST_CONSTANTS.PUBLISHER_ID) + ).rejects.toThrow(NOTIFICATION_TEST_CONSTANTS.ERROR_PUBLISHER_NOT_FOUND); + }); + }); });