From 77721fc54d0a1d427d64550b98beca6656da065f Mon Sep 17 00:00:00 2001
From: Peter Hooper <diversemix@gmail.com>
Date: Tue, 31 Dec 2019 20:24:33 +0000
Subject: [PATCH] End of day

---
 src/event-bus/event-bus.test.ts             | 27 +++++++++
 src/event-bus/event-factory.test.ts         | 62 +++++++++++++++++++++
 src/event-bus/event-factory.ts              | 15 ++++-
 src/event-bus/types.ts                      | 15 +++--
 src/mock-event-bus/index.test.ts            | 43 ++++++--------
 src/mock-event-bus/index.ts                 |  9 +--
 src/rabbit-event-bus/amqp-connector.test.ts |  2 +-
 7 files changed, 129 insertions(+), 44 deletions(-)
 create mode 100644 src/event-bus/event-bus.test.ts
 create mode 100644 src/event-bus/event-factory.test.ts

diff --git a/src/event-bus/event-bus.test.ts b/src/event-bus/event-bus.test.ts
new file mode 100644
index 0000000..4f4a8d1
--- /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 0000000..27e1219
--- /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 8c59039..efe410c 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 0bb8497..10ace28 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 ea740cd..edded42 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 aea9daa..86d6b48 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 ecff95b..bbdeb68 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);
-- 
GitLab