@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
text/typescript
/**
* 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');
});
});
});