feat: add Slack notification provider support

- Updated the notification provider types to include 'slack'.
- Modified API tests to handle 'slack' as a valid provider type.
- Enhanced frontend forms to display Slack-specific fields (webhook URL and channel name).
- Implemented CRUD operations for Slack providers, ensuring proper payload structure.
- Added E2E tests for Slack notification provider, covering form rendering, validation, and security checks.
- Updated translations to include Slack-related text.
- Ensured that sensitive information (like tokens) is not exposed in API responses.
This commit is contained in:
GitHub Actions
2026-03-13 03:40:02 +00:00
parent fb9b6cae76
commit 26be592f4d
27 changed files with 3050 additions and 1296 deletions
@@ -16,7 +16,7 @@ vi.mock('react-i18next', () => ({
}))
vi.mock('../../api/notifications', () => ({
SUPPORTED_NOTIFICATION_PROVIDER_TYPES: ['discord', 'gotify', 'webhook', 'email', 'telegram'],
SUPPORTED_NOTIFICATION_PROVIDER_TYPES: ['discord', 'gotify', 'webhook', 'email', 'telegram', 'slack'],
getProviders: vi.fn(),
createProvider: vi.fn(),
updateProvider: vi.fn(),
@@ -148,8 +148,8 @@ describe('Notifications', () => {
const typeSelect = screen.getByTestId('provider-type') as HTMLSelectElement
const options = Array.from(typeSelect.options)
expect(options).toHaveLength(5)
expect(options.map((option) => option.value)).toEqual(['discord', 'gotify', 'webhook', 'email', 'telegram'])
expect(options).toHaveLength(6)
expect(options.map((option) => option.value)).toEqual(['discord', 'gotify', 'webhook', 'email', 'telegram', 'slack'])
expect(typeSelect.disabled).toBe(false)
})
@@ -428,8 +428,8 @@ describe('Notifications', () => {
const legacyProvider: NotificationProvider = {
...baseProvider,
id: 'legacy-provider',
name: 'Legacy Slack',
type: 'slack',
name: 'Legacy Pushover',
type: 'pushover',
enabled: false,
}
@@ -611,4 +611,62 @@ describe('Notifications', () => {
expect(screen.getByTestId('provider-config')).toBeInTheDocument()
})
it('shows token field when slack type selected', async () => {
const user = userEvent.setup()
renderWithQueryClient(<Notifications />)
await user.click(await screen.findByTestId('add-provider-btn'))
await user.selectOptions(screen.getByTestId('provider-type'), 'slack')
expect(screen.getByTestId('provider-gotify-token')).toBeInTheDocument()
})
it('hides token field when switching from slack to discord', async () => {
const user = userEvent.setup()
renderWithQueryClient(<Notifications />)
await user.click(await screen.findByTestId('add-provider-btn'))
await user.selectOptions(screen.getByTestId('provider-type'), 'slack')
expect(screen.getByTestId('provider-gotify-token')).toBeInTheDocument()
await user.selectOptions(screen.getByTestId('provider-type'), 'discord')
expect(screen.queryByTestId('provider-gotify-token')).toBeNull()
})
it('submits slack provider with token as webhook URL', async () => {
const user = userEvent.setup()
renderWithQueryClient(<Notifications />)
await user.click(await screen.findByTestId('add-provider-btn'))
await user.selectOptions(screen.getByTestId('provider-type'), 'slack')
await user.type(screen.getByTestId('provider-name'), 'Slack Alerts')
await user.type(screen.getByTestId('provider-gotify-token'), 'https://hooks.slack.com/services/T00/B00/xxx')
await user.click(screen.getByTestId('provider-save-btn'))
await waitFor(() => {
expect(notificationsApi.createProvider).toHaveBeenCalled()
})
const payload = vi.mocked(notificationsApi.createProvider).mock.calls[0][0]
expect(payload.type).toBe('slack')
expect(payload.token).toBe('https://hooks.slack.com/services/T00/B00/xxx')
})
it('does not require URL for slack', async () => {
const user = userEvent.setup()
renderWithQueryClient(<Notifications />)
await user.click(await screen.findByTestId('add-provider-btn'))
await user.selectOptions(screen.getByTestId('provider-type'), 'slack')
await user.type(screen.getByTestId('provider-name'), 'Slack No URL')
await user.type(screen.getByTestId('provider-gotify-token'), 'https://hooks.slack.com/services/T00/B00/xxx')
await user.click(screen.getByTestId('provider-save-btn'))
await waitFor(() => {
expect(notificationsApi.createProvider).toHaveBeenCalled()
})
expect(screen.queryByTestId('provider-url-error')).toBeNull()
})
})