UNPKG

@sailboat-computer/event-bus

Version:

Standardized event bus for sailboat computer v3 with resilience features and offline capabilities

432 lines (361 loc) 12.6 kB
/** * Memory adapter tests */ import { MemoryAdapter, createMemoryAdapter } from '../../src/adapters'; import { EventPriority, EventCategory } from '../../src/types'; describe('MemoryAdapter', () => { let adapter: MemoryAdapter; beforeEach(() => { // Create adapter adapter = createMemoryAdapter(); }); afterEach(async () => { try { await adapter.close(); } catch (error) { // Ignore errors } }); describe('initialize', () => { it('should initialize the adapter', async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: false }); expect(adapter.isConnected()).toBe(true); }); it('should initialize with simulated latency', async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: true, latencyRange: [10, 50] }); expect(adapter.isConnected()).toBe(true); }); }); describe('publish', () => { beforeEach(async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: false }); }); it('should publish an event', async () => { const eventId = await adapter.publish({ id: 'test-id', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); expect(eventId).toBe('test-id'); }); it('should publish and receive an event', async () => { const handler = jest.fn(); // Subscribe to the event const subscription = await adapter.subscribe('test.event', handler); // Publish an event await adapter.publish({ id: 'test-id', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that the handler was called expect(handler).toHaveBeenCalledTimes(1); expect(handler.mock.calls[0][0].id).toBe('test-id'); expect(handler.mock.calls[0][0].type).toBe('test.event'); expect(handler.mock.calls[0][0].data).toEqual({ message: 'Hello, world!' }); }); it('should handle simulated latency', async () => { // Re-initialize with simulated latency await adapter.close(); adapter = createMemoryAdapter(); await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: true, latencyRange: [50, 100] }); const handler = jest.fn(); // Subscribe to the event const subscription = await adapter.subscribe('test.event', handler); // Record start time const startTime = Date.now(); // Publish an event await adapter.publish({ id: 'test-id', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 200)); // Check that the handler was called expect(handler).toHaveBeenCalledTimes(1); // Check that the processing took at least the minimum latency const processingTime = Date.now() - startTime; expect(processingTime).toBeGreaterThanOrEqual(50); }); }); describe('subscribe', () => { beforeEach(async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: false }); }); it('should subscribe to an event', async () => { const handler = jest.fn(); const subscription = await adapter.subscribe('test.event', handler); expect(subscription).toBeDefined(); expect(subscription.id).toBeDefined(); expect(subscription.eventType).toBe('test.event'); expect(typeof subscription.unsubscribe).toBe('function'); }); it('should unsubscribe from an event', async () => { const handler = jest.fn(); const subscription = await adapter.subscribe('test.event', handler); // Publish an event await adapter.publish({ id: 'test-id-1', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that the handler was called expect(handler).toHaveBeenCalledTimes(1); // Unsubscribe await subscription.unsubscribe(); // Publish another event await adapter.publish({ id: 'test-id-2', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello again!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that the handler was not called again expect(handler).toHaveBeenCalledTimes(1); }); it('should handle multiple subscribers for the same event type', async () => { const handler1 = jest.fn(); const handler2 = jest.fn(); // Subscribe to the event const subscription1 = await adapter.subscribe('test.event', handler1); const subscription2 = await adapter.subscribe('test.event', handler2); // Publish an event await adapter.publish({ id: 'test-id', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that both handlers were called expect(handler1).toHaveBeenCalledTimes(1); expect(handler2).toHaveBeenCalledTimes(1); }); it('should handle wildcard subscriptions', async () => { const handler = jest.fn(); // Subscribe to all events const subscription = await adapter.subscribe('*', handler); // Publish events of different types await adapter.publish({ id: 'test-id-1', type: 'test.event.one', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Event one' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); await adapter.publish({ id: 'test-id-2', type: 'test.event.two', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Event two' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the events to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that the handler was called for both events expect(handler).toHaveBeenCalledTimes(2); expect(handler.mock.calls[0][0].type).toBe('test.event.one'); expect(handler.mock.calls[1][0].type).toBe('test.event.two'); }); it('should handle pattern subscriptions', async () => { const handler = jest.fn(); // Subscribe to events matching a pattern const subscription = await adapter.subscribe('test.event.*', handler); // Publish events of different types await adapter.publish({ id: 'test-id-1', type: 'test.event.one', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Event one' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); await adapter.publish({ id: 'test-id-2', type: 'test.other.two', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Event two' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the events to be processed await new Promise(resolve => setTimeout(resolve, 100)); // Check that the handler was called only for the matching event expect(handler).toHaveBeenCalledTimes(1); expect(handler.mock.calls[0][0].type).toBe('test.event.one'); }); }); describe('acknowledgeEvent', () => { beforeEach(async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: false }); }); it('should acknowledge an event', async () => { // This is a no-op for memory adapter, but should not throw await expect(adapter.acknowledgeEvent('test-id', 'test.event')).resolves.not.toThrow(); }); }); describe('close', () => { beforeEach(async () => { await adapter.initialize({ serviceName: 'test-service', eventTtl: 3600000, simulateLatency: false }); }); it('should close the adapter', async () => { await adapter.close(); expect(adapter.isConnected()).toBe(false); }); it('should handle multiple close calls', async () => { await adapter.close(); // Should not throw await expect(adapter.close()).resolves.not.toThrow(); }); }); describe('event TTL', () => { beforeEach(async () => { // Initialize with a short TTL await adapter.initialize({ serviceName: 'test-service', eventTtl: 100, // 100ms simulateLatency: false }); }); it('should expire events after TTL', async () => { const handler = jest.fn(); // Subscribe to the event const subscription = await adapter.subscribe('test.event', handler); // Publish an event await adapter.publish({ id: 'test-id', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello, world!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 50)); // Check that the handler was called expect(handler).toHaveBeenCalledTimes(1); // Reset the mock handler.mockReset(); // Wait for the TTL to expire await new Promise(resolve => setTimeout(resolve, 100)); // Publish another event to trigger cleanup await adapter.publish({ id: 'test-id-2', type: 'test.event', timestamp: new Date(), source: 'test-service', version: '1.0', data: { message: 'Hello again!' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }); // Wait for the event to be processed await new Promise(resolve => setTimeout(resolve, 50)); // Check that the handler was called only for the new event expect(handler).toHaveBeenCalledTimes(1); expect(handler.mock.calls[0][0].id).toBe('test-id-2'); }); }); });