UNPKG

@eventcatalog/notifier

Version:

CLI tool to detect EventCatalog changes and send notifications

427 lines (393 loc) 12.2 kB
import { NotifierConfig, Notification } from '../src/types'; import { sendNotifications } from '../src/notifier'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import * as yaml from 'js-yaml'; import axios from 'axios'; vi.mock('axios'); const mockedAxios = vi.mocked(axios); const mockedAxiosPost = vi.mocked(axios.post); describe('Notifier', () => { beforeEach(() => { vi.clearAllMocks(); }); it('sends slack notifications to configured owners for matching events (actual sending)', async () => { mockedAxiosPost.mockResolvedValue({ status: 200 } as any); const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: dboyne: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com payments-team: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: false, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).toHaveBeenCalledTimes(2); expect(mockedAxiosPost).toHaveBeenCalledWith( 'https://fake-slack-webhook.com', expect.objectContaining({ text: '🆕 EventCatalog - ✉️ New Event Consumer Added', attachments: expect.arrayContaining([ expect.objectContaining({ color: 'good', pretext: 'A new service has started consuming an event in your architecture', fields: expect.arrayContaining([ expect.objectContaining({ title: '📡 Event Being Consumed', value: expect.stringContaining('Payment Complete'), }), ]), }), ]), }), expect.objectContaining({ headers: {}, }) ); // Notification sent successfully - no need to test console output }); it('logs notifications in dry run mode without sending', async () => { const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: payments-team: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: true, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).not.toHaveBeenCalled(); }); it('does not send notifications for events not configured for any owner', async () => { const notifications = [ { id: 'order-created', // Different event type resource: { id: 'OrderCreated', name: 'Order Created', version: '0.0.1', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'OrderService', name: 'Order Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: payments-team: events: - consumer-added # Only interested in consumer-added events channels: - type: slack webhook: https://fake-slack-webhook.com `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: true, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).not.toHaveBeenCalled(); }); it('sends notifications to multiple channels for the same owner', async () => { const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: payments-team: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook-1.com - type: slack webhook: https://fake-slack-webhook-2.com `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: true, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).not.toHaveBeenCalled(); }); it('handles HTTP errors when sending notifications', async () => { const error = new Error('Network error'); mockedAxiosPost.mockRejectedValue(error); const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: dboyne: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await expect( sendNotifications(config, notifications, { dryRun: false, lifecycle: 'active', actionUrl: 'https://example.com' }) ).rejects.toThrow('Network error'); }); it('sends notifications with custom headers when specified', async () => { mockedAxiosPost.mockResolvedValue({ status: 200 } as any); const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: dboyne: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com headers: Authorization: Bearer secret-token X-Custom-Header: custom-value `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: false, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).toHaveBeenCalledWith( 'https://fake-slack-webhook.com', expect.objectContaining({ text: '🆕 EventCatalog - ✉️ New Event Consumer Added', }), expect.objectContaining({ headers: { Authorization: 'Bearer secret-token', 'X-Custom-Header': 'custom-value', }, }) ); }); it('logs headers in dry run mode when custom headers are specified', async () => { const notifications = [ { id: 'consumer-added', resource: { id: 'PaymentComplete', name: 'Payment Complete', version: '0.0.2', type: 'event', owners: [ { id: 'dboyne', }, ], }, consumer: { id: 'PaymentService', name: 'Payment Service', version: '0.0.1', type: 'service', owners: [ { id: 'dboyne', }, ], }, metadata: { timestamp: '2025-07-23T09:49:01.605Z', catalog_path: '/Users/dboyne/dev/eventcatalog/eventcatalog-notifier/catalog-example', }, }, ] as Notification[]; const CONFIGURATION = ` version: 1.0.0 eventcatalog_url: https://eventcatalog.example.com owners: payments-team: events: - consumer-added channels: - type: slack webhook: https://fake-slack-webhook.com headers: Authorization: Bearer secret-token `; const config = yaml.load(CONFIGURATION) as NotifierConfig; await sendNotifications(config, notifications, { dryRun: true, lifecycle: 'active', actionUrl: 'https://example.com' }); expect(mockedAxiosPost).not.toHaveBeenCalled(); }); });