import { beforeEach, describe, expect, it, vi } from 'vitest'; const { dispatchToTargetsMock } = vi.hoisted(() => ({ dispatchToTargetsMock: vi.fn(async () => []), })); vi.mock('@bytelyst/webhook-dispatch', () => ({ dispatchToTargets: dispatchToTargetsMock, })); import { _resetEventBus, getEventBus } from './event-bus.js'; import { _resetWebhookSubscriberForTests, initWebhookSubscriber, isWebhookSubscriberRunning, registerWebhookTarget, stopWebhookSubscriber, } from './webhook-subscriber.js'; describe('webhook subscriber lifecycle', () => { beforeEach(() => { _resetWebhookSubscriberForTests(); _resetEventBus(); dispatchToTargetsMock.mockClear(); }); it('does not subscribe until initialized', async () => { registerWebhookTarget({ id: 'target-1', url: 'https://example.com/webhook', secret: 'secret', events: ['note.created'], enabled: true, }); await getEventBus().emit('note.created', { noteId: 'note-1', workspaceId: 'ws-1', userId: 'user-1', title: 'Created', }); expect(isWebhookSubscriberRunning()).toBe(false); expect(dispatchToTargetsMock).not.toHaveBeenCalled(); }); it('initializes idempotently and unsubscribes on stop', async () => { registerWebhookTarget({ id: 'target-1', url: 'https://example.com/webhook', secret: 'secret', events: ['note.created'], enabled: true, }); initWebhookSubscriber(); initWebhookSubscriber(); expect(isWebhookSubscriberRunning()).toBe(true); await getEventBus().emit('note.created', { noteId: 'note-1', workspaceId: 'ws-1', userId: 'user-1', title: 'Created', }); expect(dispatchToTargetsMock).toHaveBeenCalledTimes(1); stopWebhookSubscriber(); expect(isWebhookSubscriberRunning()).toBe(false); await getEventBus().emit('note.created', { noteId: 'note-2', workspaceId: 'ws-1', userId: 'user-1', title: 'Created again', }); expect(dispatchToTargetsMock).toHaveBeenCalledTimes(1); }); });