1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-11-01 19:07:38 +01:00
unleash.unleash/src/lib/addons/slack-app.test.ts
Nuno Góis 1dcb33d4e7
fix: simplify channels logic in slack app integration (#4756)
https://linear.app/unleash/issue/2-1393/drop-the-always-post-to-default-channels-field

This drops the "Always post to default channels" field in the Slack App
integration in favor of always posting to the configured channels. This
should simplify the configuration of this integration.

Here's a breakdown of the logic with this change:
 - Always post to the configured Slack channels, regardless of tags;
- Tags are still respected. E.g. if we have a configured channel
"channel-1" and a tag for "channel-2", then we post to both channels;
- As channels are optional, if you would like to skip default channels
for certain events and handle everything through tags, you can just
create a new configuration without any default channels;

This also updates the labels and changes the tests to better reflect the
intended behavior.


![image](https://github.com/Unleash/unleash/assets/14320932/a2427bdd-4b92-44b3-9bad-8adb0f94c34d)
2023-09-18 08:32:37 +01:00

180 lines
5.4 KiB
TypeScript

import { IEvent, FEATURE_ENVIRONMENT_ENABLED } from '../types/events';
import SlackAppAddon from './slack-app';
import { ChatPostMessageArguments, ErrorCode } from '@slack/web-api';
const slackApiCalls: ChatPostMessageArguments[] = [];
let postMessage = jest.fn().mockImplementation((options) => {
slackApiCalls.push(options);
return Promise.resolve();
});
jest.mock('@slack/web-api', () => ({
WebClient: jest.fn().mockImplementation(() => ({
chat: {
postMessage,
},
on: jest.fn(),
})),
ErrorCode: {
PlatformError: 'slack_webapi_platform_error',
},
WebClientEvent: {
RATE_LIMITED: 'rate_limited',
},
}));
describe('SlackAppAddon', () => {
let addon;
const accessToken = 'test-access-token';
const loggerMock = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
};
const getLogger = jest.fn(() => loggerMock);
const mockError = {
code: ErrorCode.PlatformError,
data: 'Platform error message',
};
const event: IEvent = {
id: 1,
createdAt: new Date(),
type: FEATURE_ENVIRONMENT_ENABLED,
createdBy: 'some@user.com',
project: 'default',
featureName: 'some-toggle',
environment: 'development',
data: {
name: 'some-toggle',
enabled: false,
type: 'release',
strategies: [{ name: 'default' }],
},
};
beforeEach(() => {
jest.useFakeTimers();
slackApiCalls.length = 0;
postMessage.mockClear();
addon = new SlackAppAddon({
getLogger,
unleashUrl: 'http://some-url.com',
});
});
afterEach(() => {
jest.useRealTimers();
});
it('should post message when feature is toggled', async () => {
await addon.handleEvent(event, {
accessToken,
defaultChannels: 'general',
});
expect(slackApiCalls.length).toBe(1);
expect(slackApiCalls[0].channel).toBe('general');
});
it('should post to all channels in defaultChannels', async () => {
await addon.handleEvent(event, {
accessToken,
defaultChannels: 'general, another-channel-1',
});
expect(slackApiCalls.length).toBe(2);
expect(slackApiCalls[0].channel).toBe('general');
expect(slackApiCalls[1].channel).toBe('another-channel-1');
});
it('should post to all channels in tags', async () => {
const eventWith2Tags: IEvent = {
...event,
tags: [
{ type: 'slack', value: 'general' },
{ type: 'slack', value: 'another-channel-1' },
],
};
await addon.handleEvent(eventWith2Tags, { accessToken });
expect(slackApiCalls.length).toBe(2);
expect(slackApiCalls[0].channel).toBe('general');
expect(slackApiCalls[1].channel).toBe('another-channel-1');
});
it('should concatenate defaultChannels and channels in tags to post to all unique channels found', async () => {
const eventWith2Tags: IEvent = {
...event,
tags: [
{ type: 'slack', value: 'general' },
{ type: 'slack', value: 'another-channel-1' },
],
};
await addon.handleEvent(eventWith2Tags, {
accessToken,
defaultChannels: 'another-channel-1, another-channel-2',
});
expect(slackApiCalls.length).toBe(3);
expect(slackApiCalls[0].channel).toBe('general');
expect(slackApiCalls[1].channel).toBe('another-channel-1');
expect(slackApiCalls[2].channel).toBe('another-channel-2');
});
it('should not post a message if there are no tagged channels and no defaultChannels', async () => {
await addon.handleEvent(event, {
accessToken,
});
expect(slackApiCalls.length).toBe(0);
});
it('should log error when an API call fails', async () => {
postMessage = jest.fn().mockRejectedValue(mockError);
await addon.handleEvent(event, {
accessToken,
defaultChannels: 'general',
});
expect(loggerMock.warn).toHaveBeenCalledWith(
`Error handling event ${event.type}. A platform error occurred: Platform error message`,
expect.any(Object),
);
});
it('should handle rejections in chat.postMessage', async () => {
const eventWith3Tags: IEvent = {
...event,
tags: [
{ type: 'slack', value: 'general' },
{ type: 'slack', value: 'another-channel-1' },
{ type: 'slack', value: 'another-channel-2' },
],
};
postMessage = jest
.fn()
.mockResolvedValueOnce({ ok: true })
.mockResolvedValueOnce({ ok: true })
.mockRejectedValueOnce(mockError);
await addon.handleEvent(eventWith3Tags, { accessToken });
expect(postMessage).toHaveBeenCalledTimes(3);
expect(loggerMock.warn).toHaveBeenCalledWith(
`Error handling event ${FEATURE_ENVIRONMENT_ENABLED}. A platform error occurred: Platform error message`,
expect.any(Object),
);
expect(loggerMock.info).toHaveBeenCalledWith(
`Handled event ${FEATURE_ENVIRONMENT_ENABLED} dispatching 2 out of 3 messages successfully.`,
);
});
});