/* eslint-disable @typescript-eslint/no-unused-vars */ /** * Testes unitários para baileys-event-stream.ts */ import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals' import { BaileysEventStream, createEventStream, eventFilters, type EventPriority, eventTransformers, type StreamEvent } from '../../Utils/baileys-event-stream.js' describe('BaileysEventStream', () => { let stream: BaileysEventStream beforeEach(() => { stream = createEventStream({ maxBufferSize: 100, batchSize: 10, collectMetrics: false }) }) afterEach(() => { stream.destroy() }) describe('push events', () => { it('should push event to stream', () => { stream.pause() const result = stream.push('messages.upsert', { message: 'test' }) expect(result).toBe(true) expect(stream.getStats().bufferSize).toBeGreaterThan(0) stream.resume() }) it('should assign priority based on event type', done => { stream.on('*', event => { expect(event.priority).toBe('critical') done() }) stream.push('connection.update', { state: 'open' }) }) it('should use custom priority when provided', done => { stream.on('*', event => { expect(event.priority).toBe('low') done() }) stream.push('messages.upsert', { message: 'test' }, { priority: 'low' }) }) it('should assign correct category', done => { stream.on('*', event => { expect(event.category).toBe('message') done() }) stream.push('messages.upsert', { message: 'test' }) }) it('should reject events when buffer is full', () => { const smallStream = createEventStream({ maxBufferSize: 5, enableBackpressure: true, highWaterMark: 3, collectMetrics: false }) smallStream.pause() // Prevent processing for (let i = 0; i < 5; i++) { smallStream.push('messages.upsert', { index: i }) } const result = smallStream.push('messages.upsert', { overflow: true }) expect(result).toBe(false) expect(smallStream.getStats().totalDropped).toBe(1) smallStream.destroy() }) }) describe('event handlers', () => { it('should call handler for specific event type', async () => { const handler = jest.fn() as any stream.on('messages.upsert', handler) stream.push('messages.upsert', { message: 'test' }) // Wait for processing await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalledTimes(1) }) it('should call global handler for all events', async () => { const handler = jest.fn() as any stream.on('*', handler) stream.push('messages.upsert', { message: 'test' }) stream.push('connection.update', { state: 'open' }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalledTimes(2) }) it('should support once handler', async () => { const handler = jest.fn() as any stream.once('messages.upsert', handler) stream.push('messages.upsert', { first: true }) stream.push('messages.upsert', { second: true }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalledTimes(1) }) it('should remove handler with off', async () => { const handler = jest.fn() as any stream.on('messages.upsert', handler) stream.off('messages.upsert', handler) stream.push('messages.upsert', { message: 'test' }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).not.toHaveBeenCalled() }) }) describe('pause and resume', () => { it('should pause processing', async () => { const handler = jest.fn() as any stream.on('messages.upsert', handler) stream.pause() expect(stream.isPaused()).toBe(true) stream.push('messages.upsert', { message: 'test' }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).not.toHaveBeenCalled() }) it('should resume processing', async () => { const handler = jest.fn() as any stream.on('messages.upsert', handler) stream.pause() stream.push('messages.upsert', { message: 'test' }) stream.resume() await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalled() }) }) describe('flush', () => { it('should process all buffered events', async () => { const handler = jest.fn() as any stream.on('messages.upsert', handler) for (let i = 0; i < 5; i++) { stream.push('messages.upsert', { index: i }) } // Wait for async processing await new Promise(resolve => setTimeout(resolve, 100)) expect(handler).toHaveBeenCalledTimes(5) }) }) describe('filters', () => { it('should filter events before processing', async () => { const handler = jest.fn() as any stream.addFilter(event => (event.data as any).include === true) stream.on('*', handler) stream.push('messages.upsert', { include: true }) stream.push('messages.upsert', { include: false }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalledTimes(1) }) it('should remove filter', async () => { const filter = (_event: StreamEvent) => false stream.addFilter(filter) stream.removeFilter(filter) const handler = jest.fn() as any stream.on('*', handler) stream.push('messages.upsert', { test: true }) await new Promise(resolve => setTimeout(resolve, 50)) expect(handler).toHaveBeenCalled() }) }) describe('transformers', () => { it('should transform events before processing', async () => { stream.addTransformer(event => ({ ...event, metadata: { ...event.metadata, transformed: true } })) let receivedEvent: StreamEvent | null = null stream.on('*', event => { receivedEvent = event }) stream.push('messages.upsert', { message: 'test' }) await new Promise(resolve => setTimeout(resolve, 50)) expect((receivedEvent as any)?.metadata?.transformed).toBe(true) }) }) describe('dead letter queue', () => { it('should move failed events to DLQ after max retries', async () => { const failingHandler = jest.fn(() => { throw new Error('Processing failed') }) const dlqStream = createEventStream({ maxRetries: 2, batchSize: 1, collectMetrics: false }) dlqStream.on('messages.upsert', failingHandler) dlqStream.push('messages.upsert', { will: 'fail' }) await new Promise(resolve => setTimeout(resolve, 200)) const dlq = dlqStream.getDeadLetterQueue() expect(dlq.length).toBeGreaterThan(0) dlqStream.destroy() }) it('should clear DLQ', () => { stream.push('test', { data: 'test' }) // Manually add to DLQ for testing const dlq = stream.getDeadLetterQueue() stream.clearDeadLetterQueue() expect(stream.getStats().deadLetterQueueSize).toBe(0) }) }) describe('statistics', () => { it('should track event statistics', async () => { stream.on('*', () => {}) stream.push('messages.upsert', { a: 1 }) stream.push('connection.update', { b: 2 }) stream.push('messages.update', { c: 3 }) await new Promise(resolve => setTimeout(resolve, 50)) const stats = stream.getStats() expect(stats.totalReceived).toBe(3) expect(stats.totalProcessed).toBe(3) expect(stats.eventsByType['messages.upsert']).toBe(1) expect(stats.eventsByType['connection.update']).toBe(1) }) it('should reset statistics', async () => { stream.on('*', () => {}) stream.push('messages.upsert', { test: true }) await new Promise(resolve => setTimeout(resolve, 50)) stream.resetStats() const stats = stream.getStats() expect(stats.totalReceived).toBe(0) expect(stats.totalProcessed).toBe(0) }) }) describe('priority ordering', () => { it('should process critical events before normal events', async () => { const processedOrder: EventPriority[] = [] stream.pause() stream.on('*', event => { processedOrder.push(event.priority) }) // Add in reverse priority order stream.push('presence.update', {}, { priority: 'low' }) stream.push('messages.upsert', {}, { priority: 'normal' }) stream.push('connection.update', {}, { priority: 'critical' }) stream.push('call', {}, { priority: 'high' }) stream.resume() await new Promise(resolve => setTimeout(resolve, 50)) await stream.flush() expect(processedOrder[0]).toBe('critical') expect(processedOrder[1]).toBe('high') expect(processedOrder[2]).toBe('normal') expect(processedOrder[3]).toBe('low') }) }) describe('backpressure', () => { it('should emit backpressure event when high water mark reached', done => { const bpStream = createEventStream({ maxBufferSize: 100, highWaterMark: 5, enableBackpressure: true, collectMetrics: false }) bpStream.pause() bpStream.on('backpressure', () => { expect(bpStream.getStats().isBackpressured).toBe(true) bpStream.destroy() done() }) for (let i = 0; i < 10; i++) { bpStream.push('messages.upsert', { index: i }) } }) it('should emit drain event when below low water mark', done => { const bpStream = createEventStream({ maxBufferSize: 100, highWaterMark: 5, lowWaterMark: 2, enableBackpressure: true, batchSize: 10, collectMetrics: false }) bpStream.pause() for (let i = 0; i < 10; i++) { bpStream.push('messages.upsert', { index: i }) } bpStream.on('*', () => {}) bpStream.on('drain', () => { bpStream.destroy() done() }) bpStream.resume() }) }) describe('clear', () => { it('should clear buffer', () => { stream.pause() for (let i = 0; i < 10; i++) { stream.push('messages.upsert', { index: i }) } stream.clear() expect(stream.getStats().bufferSize).toBe(0) }) }) }) describe('eventFilters', () => { describe('byType', () => { it('should filter by event type', () => { const filter = eventFilters.byType('messages.upsert', 'messages.update') const matchingEvent: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'normal', category: 'message' } const nonMatchingEvent: StreamEvent = { id: '2', type: 'connection.update', data: {}, timestamp: Date.now(), priority: 'normal', category: 'connection' } expect(filter(matchingEvent)).toBe(true) expect(filter(nonMatchingEvent)).toBe(false) }) }) describe('byCategory', () => { it('should filter by category', () => { const filter = eventFilters.byCategory('message', 'connection') const matchingEvent: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'normal', category: 'message' } const nonMatchingEvent: StreamEvent = { id: '2', type: 'presence.update', data: {}, timestamp: Date.now(), priority: 'normal', category: 'presence' } expect(filter(matchingEvent)).toBe(true) expect(filter(nonMatchingEvent)).toBe(false) }) }) describe('byMinPriority', () => { it('should filter by minimum priority', () => { const filter = eventFilters.byMinPriority('high') const criticalEvent: StreamEvent = { id: '1', type: 'connection.update', data: {}, timestamp: Date.now(), priority: 'critical', category: 'connection' } const lowEvent: StreamEvent = { id: '2', type: 'presence.update', data: {}, timestamp: Date.now(), priority: 'low', category: 'presence' } expect(filter(criticalEvent)).toBe(true) expect(filter(lowEvent)).toBe(false) }) }) describe('recentOnly', () => { it('should filter old events', () => { const filter = eventFilters.recentOnly(1000) const recentEvent: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'normal', category: 'message' } const oldEvent: StreamEvent = { id: '2', type: 'messages.upsert', data: {}, timestamp: Date.now() - 5000, priority: 'normal', category: 'message' } expect(filter(recentEvent)).toBe(true) expect(filter(oldEvent)).toBe(false) }) }) describe('and', () => { it('should combine filters with AND', () => { const filter = eventFilters.and(eventFilters.byType('messages.upsert'), eventFilters.byMinPriority('high')) const matchingEvent: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'high', category: 'message' } const partialMatch: StreamEvent = { id: '2', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'low', category: 'message' } expect(filter(matchingEvent)).toBe(true) expect(filter(partialMatch)).toBe(false) }) }) describe('or', () => { it('should combine filters with OR', () => { const filter = eventFilters.or(eventFilters.byType('messages.upsert'), eventFilters.byCategory('connection')) const typeMatch: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'normal', category: 'message' } const categoryMatch: StreamEvent = { id: '2', type: 'connection.update', data: {}, timestamp: Date.now(), priority: 'normal', category: 'connection' } const noMatch: StreamEvent = { id: '3', type: 'presence.update', data: {}, timestamp: Date.now(), priority: 'normal', category: 'presence' } expect(filter(typeMatch)).toBe(true) expect(filter(categoryMatch)).toBe(true) expect(filter(noMatch)).toBe(false) }) }) }) describe('eventTransformers', () => { describe('addProcessingTimestamp', () => { it('should add processing timestamp', () => { const transformer = eventTransformers.addProcessingTimestamp() const event: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now() - 1000, priority: 'normal', category: 'message' } const transformed = transformer(event) expect(transformed.metadata?.processingTimestamp).toBeDefined() expect(transformed.metadata?.processingTimestamp).toBeGreaterThan(event.timestamp) }) }) describe('addTraceId', () => { it('should add trace ID', () => { const transformer = eventTransformers.addTraceId(() => 'trace-123') const event: StreamEvent = { id: '1', type: 'messages.upsert', data: {}, timestamp: Date.now(), priority: 'normal', category: 'message' } const transformed = transformer(event) expect(transformed.metadata?.traceId).toBe('trace-123') }) }) describe('elevatepriorityIf', () => { it('should elevate priority when condition is met', () => { const transformer = eventTransformers.elevatepriorityIf( event => (event.data as { urgent?: boolean }).urgent === true, 'critical' ) const urgentEvent: StreamEvent = { id: '1', type: 'messages.upsert', data: { urgent: true }, timestamp: Date.now(), priority: 'normal', category: 'message' } const normalEvent: StreamEvent = { id: '2', type: 'messages.upsert', data: { urgent: false }, timestamp: Date.now(), priority: 'normal', category: 'message' } expect(transformer(urgentEvent).priority).toBe('critical') expect(transformer(normalEvent).priority).toBe('normal') }) }) })