Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/models/notification/subscriptions.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import type { OperationResponse } from '../common/types';

import type {
AllowedMode,
CategorySubscriptionUpdate,
PublisherSubscriptionUpdate,
SubscriptionGetAllOptions,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -230,4 +237,44 @@ export interface SubscriptionServiceModel {
* ```
*/
updateTopicGroup(tenantId: string, subscriptions: TopicGroupSubscriptionUpdate[]): Promise<SubscriptionUpdateTopicGroupResponse>;

/**
* 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('<tenantId>', '<publisherId>', {
* name: NotificationMode.Email,
* isActive: true,
* });
* ```
* @internal
*/
updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise<SubscriptionUpdateModeResponse>;

/**
* 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('<tenantId>', '<publisherId>');
* ```
* @internal
*/
reset(tenantId: string, publisherId: string): Promise<SubscriptionGetResponse>;
}
55 changes: 55 additions & 0 deletions src/services/notification/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { track } from '../../core/telemetry';
import { BaseService } from '../base';

import type {
AllowedMode,
CategorySubscriptionUpdate,
PublisherSubscriptionUpdate,
SubscriptionGetAllOptions,
Expand All @@ -18,6 +19,7 @@ import type {
SubscriptionGetSupportedChannelsResponse,
SubscriptionServiceModel,
SubscriptionUpdateCategoryResponse,
SubscriptionUpdateModeResponse,
SubscriptionUpdatePublisherResponse,
SubscriptionUpdateTopicGroupResponse,
SubscriptionUpdateTopicResponse,
Expand Down Expand Up @@ -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('<tenantId>', '<publisherId>', {
* name: NotificationMode.Email,
* isActive: true,
* });
* ```
* @internal
*/
@track('Subscriptions.UpdateMode')
async updateMode(tenantId: string, publisherId: string, mode: AllowedMode): Promise<SubscriptionUpdateModeResponse> {
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('<tenantId>', '<publisherId>');
* ```
* @internal
*/
@track('Subscriptions.Reset')
async reset(tenantId: string, publisherId: string): Promise<SubscriptionGetResponse> {
const response = await this.post<SubscriptionGetResponse>(SUBSCRIPTION_ENDPOINTS.RESET, {
publisherId,
}, { headers: createHeaders({ [TENANT_ID]: tenantId }) });
return response.data;
}
}
2 changes: 2 additions & 0 deletions src/utils/constants/endpoints/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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.
});
82 changes: 82 additions & 0 deletions tests/unit/services/notification/subscriptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Loading