From 21182c90e2583ee27cea3d8aae24e477375b9e79 Mon Sep 17 00:00:00 2001 From: sarthak688 <107241313+sarthak688@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:14:02 +0530 Subject: [PATCH 1/2] feat(subscriptions): add channel mode + reset (updateMode, reset) Final two methods completing the Subscriptions service: - updateMode: activate/deactivate a notification channel (Email/Slack/Teams) for a publisher. Check getSupportedChannels first. - reset: restore a publisher's subscriptions to its defaults, returning the post-reset state. Adds UPDATE_MODE + RESET endpoints, SubscriptionUpdateModeResponse type, oauth-scopes entries, 5 new unit tests, and a reset integration test. This completes the 7-PR stack onboarding the Notification + Subscriptions services (15 methods total). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oauth-scopes.md | 2 + .../notification/subscriptions.models.ts | 45 ++++++++++ src/services/notification/subscriptions.ts | 53 ++++++++++++ src/utils/constants/endpoints/notification.ts | 2 + .../subscriptions.integration.test.ts | 16 +++- .../notification/subscriptions.test.ts | 82 +++++++++++++++++++ 6 files changed, 197 insertions(+), 3 deletions(-) diff --git a/docs/oauth-scopes.md b/docs/oauth-scopes.md index 7787556ff..bdc894986 100644 --- a/docs/oauth-scopes.md +++ b/docs/oauth-scopes.md @@ -231,6 +231,8 @@ The `ConversationalAgents` scope is required for real-time WebSocket sessions (` | `updateCategory()` | `NotificationService` | | `updatePublisher()` | `NotificationService` | | `updateTopicGroup()` | `NotificationService` | +| `updateMode()` | `NotificationService` | +| `reset()` | `NotificationService` | ## Processes diff --git a/src/models/notification/subscriptions.models.ts b/src/models/notification/subscriptions.models.ts index cda86722b..b4131b379 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,42 @@ 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, + * }); + * ``` + */ + 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('', ''); + * ``` + */ + reset(tenantId: string, publisherId: string): Promise; } diff --git a/src/services/notification/subscriptions.ts b/src/services/notification/subscriptions.ts index 7c7be7546..ef08ffaff 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,55 @@ 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, + * }); + * ``` + */ + @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('', ''); + * ``` + */ + @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); + }); + }); }); From 1f7d4bf79293b60097e41e4d243f88a344adefb7 Mon Sep 17 00:00:00 2001 From: sarthak688 <107241313+sarthak688@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:36:47 +0530 Subject: [PATCH 2/2] fix(subscriptions): tag updateMode/reset @internal + remove oauth-scopes entries Final piece of the @internal treatment across the 7-PR stack. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/oauth-scopes.md | 2 -- src/models/notification/subscriptions.models.ts | 2 ++ src/services/notification/subscriptions.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/oauth-scopes.md b/docs/oauth-scopes.md index bdc894986..7787556ff 100644 --- a/docs/oauth-scopes.md +++ b/docs/oauth-scopes.md @@ -231,8 +231,6 @@ The `ConversationalAgents` scope is required for real-time WebSocket sessions (` | `updateCategory()` | `NotificationService` | | `updatePublisher()` | `NotificationService` | | `updateTopicGroup()` | `NotificationService` | -| `updateMode()` | `NotificationService` | -| `reset()` | `NotificationService` | ## Processes diff --git a/src/models/notification/subscriptions.models.ts b/src/models/notification/subscriptions.models.ts index b4131b379..59f218e7c 100644 --- a/src/models/notification/subscriptions.models.ts +++ b/src/models/notification/subscriptions.models.ts @@ -258,6 +258,7 @@ export interface SubscriptionServiceModel { * isActive: true, * }); * ``` + * @internal */ updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise; @@ -273,6 +274,7 @@ export interface SubscriptionServiceModel { * ```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 ef08ffaff..43c10f2f9 100644 --- a/src/services/notification/subscriptions.ts +++ b/src/services/notification/subscriptions.ts @@ -286,6 +286,7 @@ export class SubscriptionService extends BaseService implements SubscriptionServ * isActive: true, * }); * ``` + * @internal */ @track('Subscriptions.UpdateMode') async updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise { @@ -308,6 +309,7 @@ export class SubscriptionService extends BaseService implements SubscriptionServ * ```typescript * const { publishers } = await subscriptions.reset('', ''); * ``` + * @internal */ @track('Subscriptions.Reset') async reset(tenantId: string, publisherId: string): Promise {