diff --git a/src/event-bus/event-bus.test.ts b/src/event-bus/event-bus.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f4a8d1efc07e083f85a873500679deac624746b --- /dev/null +++ b/src/event-bus/event-bus.test.ts @@ -0,0 +1,27 @@ +import { EventBus } from './event-bus'; + +describe('Event Bus class', () => { + it('EventBus is abstract', () => { + const create = (T): EventBus => new T(['test'], 'test'); + + const x = create(EventBus); + expect(x.eventsToHandle).toStrictEqual(['test']); + expect(x.serviceName).toStrictEqual('test'); + expect(() => x.destroy()).toThrow('x.destroy is not a function'); + }); + + it('EventBus can be extended', () => { + class EB extends EventBus { + called = false; + destroy(): Promise<void> { + this.called = true; + return Promise.resolve(); + } + } + const x = new EB(['test'], 'test'); + expect(x.eventsToHandle).toStrictEqual(['test']); + expect(x.serviceName).toStrictEqual('test'); + expect(x.destroy()).resolves.toBe(undefined); + expect(x.called).toBe(true); + }); +}); diff --git a/src/event-bus/event-factory.test.ts b/src/event-bus/event-factory.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..27e12197b6ff11d5185fb10bbfc585d247087979 --- /dev/null +++ b/src/event-bus/event-factory.test.ts @@ -0,0 +1,62 @@ +import { eventFactory, eventAbstractFactory } from './event-factory'; +import { Event, Payload } from './types'; + +describe('Event Creation', () => { + describe('eventFactory', () => { + it('creates an Event', () => { + const payload = new Payload(); + const event = eventFactory('type', payload); + expect(event.id).toHaveLength(36); + expect(event.eventType).toBe('type'); + expect(event.created instanceof Date).toBe(true); + expect(event.payload).toBe(payload); + }); + + it('fails to create an Event without a payload of type Payload', () => { + expect(() => eventFactory('type', {})).toThrow('argument payload is not of type Payload!'); + }); + }); + + describe('eventAbstractFactory', () => { + class Party extends Payload { + beer: boolean; + address: string; + } + class EventParty extends Event {} + + class GardenParty extends Payload { + wine: boolean; + } + + it('creates an Event of correct type', () => { + const payload = new Party(); + payload.beer = true; + payload.address = 'my place'; + + const event: EventParty = eventAbstractFactory('party', Party, payload); + + expect(event.id).toHaveLength(36); + expect(event.eventType).toBe('party'); + expect(event.created instanceof Date).toBe(true); + expect(event.payload).toHaveProperty('beer'); + expect(event.payload['beer']).toBe(true); + expect(event.payload).toHaveProperty('address'); + expect(event.payload['address']).toBe('my place'); + }); + + it('rejects arbitrary payload type', () => { + const payload = { address: 'my place' }; + const create = (p: object): EventParty => eventAbstractFactory('party', Party, p); + + expect(() => create(payload)).toThrow('expected payload of type Party got Object'); + }); + + it('rejects incorrect payload type', () => { + const payload = new GardenParty(); + payload.wine = true; + const create = (p: GardenParty): EventParty => eventAbstractFactory('party', Party, p); + + expect(() => create(payload)).toThrow('expected payload of type Party got GardenParty'); + }); + }); +}); diff --git a/src/event-bus/event-factory.ts b/src/event-bus/event-factory.ts index 8c59039a876b6231346be84c6498c36d98afab1b..efe410c49b709b8b08532390d70c6f9567d45720 100644 --- a/src/event-bus/event-factory.ts +++ b/src/event-bus/event-factory.ts @@ -1,7 +1,10 @@ -import { Event } from './types'; +import { Event, Payload } from './types'; import uuid = require('uuid'); -export const eventFactory = <P extends object>(eventType: string, payload: P): Event<P> => { +export const eventFactory = (eventType: string, payload: Payload): Event => { + if (!(payload instanceof Payload)) { + throw new TypeError('argument payload is not of type Payload!'); + } return { eventType, id: uuid.v4(), @@ -9,3 +12,11 @@ export const eventFactory = <P extends object>(eventType: string, payload: P): E payload, }; }; + +export const eventAbstractFactory = (typeName: string, payloadClass, p: object): Event => { + if (payloadClass.name !== p.constructor.name) { + throw new TypeError(`expected payload of type ${payloadClass.name} got ${p.constructor.name}`); + } + const event = eventFactory(typeName, p); + return event; +}; diff --git a/src/event-bus/types.ts b/src/event-bus/types.ts index 0bb8497590208fa0b91196a9f02710e4c3657867..10ace28347163e3847de0959326f99830fa1ff38 100644 --- a/src/event-bus/types.ts +++ b/src/event-bus/types.ts @@ -1,29 +1,28 @@ // Abstract Message Queue - types and interfaces -/** - * TODO: Once agreed, add these to DefinitelyTyped so they can be shared. - */ - export type EventType = string; -export interface Event<T extends object> { +export class Payload {} + +export class Event { readonly eventType: EventType; readonly id: string; // Generated when the event is emitted readonly created: Date; - readonly payload: T; // The actual data the event is carrying. // version: has been removed - so we can remain weakly typed // context: has also been removed - if you need information about the origin // source of the event then put it in the payload. + + constructor(readonly payload: Payload) {} } export interface EventPublisher { // Promise<boolean> should this become void | exception? we only need to know if something went wrong - publish<T extends object>(event: Event<T>): Promise<boolean>; + publish(event: Event): Promise<boolean>; } export interface EventSubscriber { // handler: returns whether or not we should ack the message - subscribe<T extends object>(eventType: string, handler: (event: Event<T>) => Promise<boolean>): void; + subscribe(eventType: string, handler: (event: Event) => Promise<boolean>): void; } // This isn't generic enough diff --git a/src/mock-event-bus/index.test.ts b/src/mock-event-bus/index.test.ts index ea740cdfdbbac0ac81a8967d1069fff7b1a1cbe0..edded42223ca7b782225d3a3e1479082ec9bc7d0 100644 --- a/src/mock-event-bus/index.test.ts +++ b/src/mock-event-bus/index.test.ts @@ -1,18 +1,6 @@ -import { MockEventBus } from '.'; -import { Event, eventFactory } from '../event-bus'; - -interface TestEventPayload1 { - x: number; - y: number; -} - -interface TestEventPayload2 { - a: number; - b: number; -} - -type MockEventType1 = Event<TestEventPayload1>; -type MockEventType2 = Event<TestEventPayload2>; +import { MockEventBus } from './index'; +import { Event, Payload } from '../event-bus/types'; +import { eventFactory } from '../event-bus/event-factory'; describe('mock message queue', () => { // describe('object lifetime', () => { @@ -26,13 +14,14 @@ describe('mock message queue', () => { const eventType = 'libero:mock:test'; const mockHandler = jest.fn(async () => true); - const mockEventBus = await new MockEventBus([eventType], 'message-bus-test'); + const mockEventBus = new MockEventBus([eventType], 'message-bus-test'); - mockEventBus.subscribe<MockEventType1>(eventType, mockHandler); + mockEventBus.subscribe(eventType, mockHandler); + const payload = new Payload(); - const event = eventFactory<TestEventPayload1>(eventType, { x: 10, y: 20 }); + const event = eventFactory(eventType, payload); - mockEventBus.publish<TestEventPayload1>(event); + mockEventBus.publish(event); expect(mockHandler).toBeCalled(); expect(mockHandler.mock.calls).toEqual([[event]]); @@ -42,14 +31,14 @@ describe('mock message queue', () => { const eventType1 = 'libero:mock:test1'; const eventType2 = 'libero:mock:test2'; - const event1: MockEventType1 = { + const event1: Event = { eventType: eventType1, id: 'some-testing-event1-id', created: new Date(), payload: { x: 10, y: 20 }, }; - const event2: MockEventType2 = { + const event2: Event = { eventType: eventType2, id: 'some-testing-event2-id', created: new Date(), @@ -61,14 +50,14 @@ describe('mock message queue', () => { let receivedEvent1: object = {}; let receivedEvent2: object = {}; - const mockHandler1 = async (event: Event<TestEventPayload1>): Promise<boolean> => { + const mockHandler1 = async (event: Event): Promise<boolean> => { handler1 += 1; receivedEvent1 = event; return Promise.resolve(true); }; - const mockHandler2 = async (event: Event<TestEventPayload2>): Promise<boolean> => { + const mockHandler2 = async (event: Event): Promise<boolean> => { handler2 += 1; receivedEvent2 = event; return Promise.resolve(true); @@ -76,10 +65,10 @@ describe('mock message queue', () => { const mockEventBus = new MockEventBus([eventType1, eventType2], 'message-bus-test'); - mockEventBus.subscribe<TestEventPayload1>(eventType1, mockHandler1); - mockEventBus.subscribe<TestEventPayload2>(eventType2, mockHandler2); + mockEventBus.subscribe(eventType1, mockHandler1); + mockEventBus.subscribe(eventType2, mockHandler2); - mockEventBus.publish<TestEventPayload2>(event2); + mockEventBus.publish(event2); expect(handler1).toBe(0); expect(handler2).toBe(1); @@ -88,7 +77,7 @@ describe('mock message queue', () => { receivedEvent1 = {}; receivedEvent2 = {}; - mockEventBus.publish<TestEventPayload1>(event1); + mockEventBus.publish(event1); expect(handler1).toBe(1); expect(handler2).toBe(1); diff --git a/src/mock-event-bus/index.ts b/src/mock-event-bus/index.ts index aea9daa76fda8d5f9d504a709b90521bdb5f300a..86d6b48b2db994eeda9c77dae158bf65fe22eac3 100644 --- a/src/mock-event-bus/index.ts +++ b/src/mock-event-bus/index.ts @@ -1,5 +1,5 @@ import { EventBus, Event, EventPublisher, EventSubscriber } from '../event-bus'; -export type AnyEvent = Event<object>; +export type AnyEvent = Event; export type AnyHandler = (ev: AnyEvent) => Promise<boolean>; /** @@ -10,7 +10,7 @@ export type AnyHandler = (ev: AnyEvent) => Promise<boolean>; export class MockEventBus extends EventBus implements EventPublisher, EventSubscriber { private queues: Map<string, AnyHandler> = new Map(); - public async publish<T extends object>(event: Event<T>): Promise<boolean> { + public async publish(event: Event): Promise<boolean> { const fn = this.queues.get(`${event.eventType}`); if (fn) { if (this.eventsToHandle.includes(event.eventType)) { @@ -20,10 +20,7 @@ export class MockEventBus extends EventBus implements EventPublisher, EventSubsc return Promise.resolve(false); } - public async subscribe<T extends object>( - eventType: string, - handler: (event: Event<T>) => Promise<boolean>, - ): Promise<void> { + public async subscribe(eventType: string, handler: (event: Event) => Promise<boolean>): Promise<void> { if (!this.serviceName) { Promise.reject(`Service name not set!`); } diff --git a/src/rabbit-event-bus/amqp-connector.test.ts b/src/rabbit-event-bus/amqp-connector.test.ts index ecff95b78873e328b1453785ed2558820bb05b1c..bbdeb68debaf2d7c170434f0d7234d8bff4c2095 100644 --- a/src/rabbit-event-bus/amqp-connector.test.ts +++ b/src/rabbit-event-bus/amqp-connector.test.ts @@ -134,7 +134,7 @@ describe('AMQP connector', () => { // we need to wait for connection to be stored before we can publish await flushPromises(); - await connector.publish(event as Event<{}>); + await connector.publish(event as Event); await flushPromises(); expect(mockChannel.publish).toHaveBeenCalledTimes(1);