| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- import { firstValueFrom, Subject } from 'rxjs';
- import { QueryRunner } from 'typeorm';
- import { beforeEach, describe, expect, it, vi } from 'vitest';
- import { EventBus } from './event-bus';
- import { VendureEvent } from './vendure-event';
- class MockTransactionSubscriber {
- awaitRelease(queryRunner: QueryRunner): Promise<QueryRunner> {
- return Promise.resolve(queryRunner);
- }
- }
- describe('EventBus', () => {
- let eventBus: EventBus;
- beforeEach(() => {
- eventBus = new EventBus(new MockTransactionSubscriber() as any);
- });
- it('can publish without subscribers', () => {
- const event = new TestEvent('foo');
- expect(async () => await eventBus.publish(event)).not.toThrow();
- });
- describe('ofType()', () => {
- it('single handler is called once', async () => {
- const handler = vi.fn();
- const event = new TestEvent('foo');
- eventBus.ofType(TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- expect(handler).toHaveBeenCalledWith(event);
- });
- it('single handler is called on multiple events', async () => {
- const handler = vi.fn();
- const event1 = new TestEvent('foo');
- const event2 = new TestEvent('bar');
- const event3 = new TestEvent('baz');
- eventBus.ofType(TestEvent).subscribe(handler);
- await eventBus.publish(event1);
- await eventBus.publish(event2);
- await eventBus.publish(event3);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(3);
- expect(handler).toHaveBeenCalledWith(event1);
- expect(handler).toHaveBeenCalledWith(event2);
- expect(handler).toHaveBeenCalledWith(event3);
- });
- it('multiple handler are called', async () => {
- const handler1 = vi.fn();
- const handler2 = vi.fn();
- const handler3 = vi.fn();
- const event = new TestEvent('foo');
- eventBus.ofType(TestEvent).subscribe(handler1);
- eventBus.ofType(TestEvent).subscribe(handler2);
- eventBus.ofType(TestEvent).subscribe(handler3);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledWith(event);
- expect(handler2).toHaveBeenCalledWith(event);
- expect(handler3).toHaveBeenCalledWith(event);
- });
- it('handler is not called for other events', async () => {
- const handler = vi.fn();
- const event = new OtherTestEvent('foo');
- eventBus.ofType(TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).not.toHaveBeenCalled();
- });
- it('ofType() returns a subscription', async () => {
- const handler = vi.fn();
- const event = new TestEvent('foo');
- const subscription = eventBus.ofType(TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- subscription.unsubscribe();
- await eventBus.publish(event);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- });
- it('unsubscribe() only unsubscribes own handler', async () => {
- const handler1 = vi.fn();
- const handler2 = vi.fn();
- const event = new TestEvent('foo');
- const subscription1 = eventBus.ofType(TestEvent).subscribe(handler1);
- const subscription2 = eventBus.ofType(TestEvent).subscribe(handler2);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledTimes(1);
- expect(handler2).toHaveBeenCalledTimes(1);
- subscription1.unsubscribe();
- await eventBus.publish(event);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledTimes(1);
- expect(handler2).toHaveBeenCalledTimes(3);
- });
- });
- describe('filter()', () => {
- it('single handler is called once', async () => {
- const handler = vi.fn();
- const event = new TestEvent('foo');
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- expect(handler).toHaveBeenCalledWith(event);
- });
- it('single handler is called on multiple events', async () => {
- const handler = vi.fn();
- const event1 = new TestEvent('foo');
- const event2 = new TestEvent('bar');
- const event3 = new TestEvent('baz');
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler);
- await eventBus.publish(event1);
- await eventBus.publish(event2);
- await eventBus.publish(event3);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(3);
- expect(handler).toHaveBeenCalledWith(event1);
- expect(handler).toHaveBeenCalledWith(event2);
- expect(handler).toHaveBeenCalledWith(event3);
- });
- it('multiple handler are called', async () => {
- const handler1 = vi.fn();
- const handler2 = vi.fn();
- const handler3 = vi.fn();
- const event = new TestEvent('foo');
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler1);
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler2);
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler3);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledWith(event);
- expect(handler2).toHaveBeenCalledWith(event);
- expect(handler3).toHaveBeenCalledWith(event);
- });
- it('handler is not called for other events', async () => {
- const handler = vi.fn();
- const event = new OtherTestEvent('foo');
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).not.toHaveBeenCalled();
- });
- it('handler is called for instance of child classes', async () => {
- const handler = vi.fn();
- const event = new ChildTestEvent('bar', 'foo');
- eventBus.filter(vendureEvent => vendureEvent instanceof TestEvent).subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalled();
- });
- it('filter() returns a subscription', async () => {
- const handler = vi.fn();
- const event = new TestEvent('foo');
- const subscription = eventBus
- .filter(vendureEvent => vendureEvent instanceof TestEvent)
- .subscribe(handler);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- subscription.unsubscribe();
- await eventBus.publish(event);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler).toHaveBeenCalledTimes(1);
- });
- it('unsubscribe() only unsubscribes own handler', async () => {
- const handler1 = vi.fn();
- const handler2 = vi.fn();
- const event = new TestEvent('foo');
- const subscription1 = eventBus
- .filter(vendureEvent => vendureEvent instanceof TestEvent)
- .subscribe(handler1);
- const subscription2 = eventBus
- .filter(vendureEvent => vendureEvent instanceof TestEvent)
- .subscribe(handler2);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledTimes(1);
- expect(handler2).toHaveBeenCalledTimes(1);
- subscription1.unsubscribe();
- await eventBus.publish(event);
- await eventBus.publish(event);
- await new Promise(resolve => setImmediate(resolve));
- expect(handler1).toHaveBeenCalledTimes(1);
- expect(handler2).toHaveBeenCalledTimes(3);
- });
- });
- describe('blocking event handlers', () => {
- it('calls the handler function', async () => {
- const event = new TestEvent('foo');
- const spy = vi.fn((e: VendureEvent) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy(e),
- id: 'test-handler',
- event: TestEvent,
- });
- await eventBus.publish(event);
- expect(spy).toHaveBeenCalledTimes(1);
- expect(spy).toHaveBeenCalledWith(event);
- });
- it('throws when attempting to register with a duplicate id', () => {
- eventBus.registerBlockingEventHandler({
- handler: e => undefined,
- id: 'test-handler',
- event: TestEvent,
- });
- expect(() => {
- eventBus.registerBlockingEventHandler({
- handler: e => undefined,
- id: 'test-handler',
- event: TestEvent,
- });
- }).toThrowError(
- 'A handler with the id "test-handler" is already registered for the event TestEvent',
- );
- });
- it('calls multiple handler functions', async () => {
- const event = new TestEvent('foo');
- const spy1 = vi.fn((e: VendureEvent) => undefined);
- const spy2 = vi.fn((e: VendureEvent) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy1(e),
- id: 'test-handler1',
- event: TestEvent,
- });
- eventBus.registerBlockingEventHandler({
- handler: e => spy2(e),
- id: 'test-handler2',
- event: TestEvent,
- });
- await eventBus.publish(event);
- expect(spy1).toHaveBeenCalledTimes(1);
- expect(spy1).toHaveBeenCalledWith(event);
- expect(spy2).toHaveBeenCalledTimes(1);
- expect(spy2).toHaveBeenCalledWith(event);
- });
- it('handles multiple events', async () => {
- const event1 = new TestEvent('foo');
- const event2 = new OtherTestEvent('bar');
- const spy = vi.fn((e: VendureEvent) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy(e),
- id: 'test-handler',
- event: [TestEvent, OtherTestEvent],
- });
- await eventBus.publish(event1);
- expect(spy).toHaveBeenCalledTimes(1);
- expect(spy).toHaveBeenCalledWith(event1);
- await eventBus.publish(event2);
- expect(spy).toHaveBeenCalledTimes(2);
- expect(spy).toHaveBeenCalledWith(event2);
- });
- it('publish method throws in a handler throws', async () => {
- const event = new TestEvent('foo');
- eventBus.registerBlockingEventHandler({
- handler: () => {
- throw new Error('test error');
- },
- id: 'test-handler',
- event: TestEvent,
- });
- await expect(eventBus.publish(event)).rejects.toThrow('test error');
- });
- it('order of execution with "before" property', async () => {
- const event = new TestEvent('foo');
- const spy = vi.fn((input: string) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler1'),
- id: 'test-handler1',
- event: TestEvent,
- });
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler2'),
- id: 'test-handler2',
- event: TestEvent,
- before: 'test-handler1',
- });
- await eventBus.publish(event);
- expect(spy).toHaveBeenCalledTimes(2);
- expect(spy).toHaveBeenNthCalledWith(1, 'test-handler2');
- expect(spy).toHaveBeenNthCalledWith(2, 'test-handler1');
- });
- it('order of execution with "after" property', async () => {
- const event = new TestEvent('foo');
- const spy = vi.fn((input: string) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler1'),
- id: 'test-handler1',
- event: TestEvent,
- after: 'test-handler2',
- });
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler2'),
- id: 'test-handler2',
- event: TestEvent,
- });
- await eventBus.publish(event);
- expect(spy).toHaveBeenCalledTimes(2);
- expect(spy).toHaveBeenNthCalledWith(1, 'test-handler2');
- expect(spy).toHaveBeenNthCalledWith(2, 'test-handler1');
- });
- it('throws if there is a cycle in before ordering', () => {
- const spy = vi.fn((input: string) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler1'),
- id: 'test-handler1',
- event: TestEvent,
- before: 'test-handler2',
- });
- expect(() =>
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler2'),
- id: 'test-handler2',
- event: TestEvent,
- before: 'test-handler1',
- }),
- ).toThrowError(
- 'Circular dependency detected between event handlers test-handler1 and test-handler2',
- );
- });
- it('throws if there is a cycle in after ordering', () => {
- const spy = vi.fn((input: string) => undefined);
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler1'),
- id: 'test-handler1',
- event: TestEvent,
- after: 'test-handler2',
- });
- expect(() =>
- eventBus.registerBlockingEventHandler({
- handler: e => spy('test-handler2'),
- id: 'test-handler2',
- event: TestEvent,
- after: 'test-handler1',
- }),
- ).toThrowError(
- 'Circular dependency detected between event handlers test-handler1 and test-handler2',
- );
- });
- it('blocks execution of the publish method', async () => {
- const event = new TestEvent('foo');
- const subject = new Subject<void>();
- eventBus.registerBlockingEventHandler({
- handler: e => firstValueFrom(subject.asObservable()),
- id: 'test-handler',
- event: TestEvent,
- });
- const publishPromise = eventBus.publish(event);
- expect(publishPromise).toBeInstanceOf(Promise);
- let resolved = false;
- void publishPromise.then(() => (resolved = true));
- expect(resolved).toBe(false);
- await new Promise(resolve => setTimeout(resolve, 50));
- expect(resolved).toBe(false);
- // Handler only resolves after the subject emits
- subject.next();
- // Allow the event loop to tick
- await new Promise(resolve => setTimeout(resolve, 0));
- // Now the promise should be resolved
- expect(resolved).toBe(true);
- });
- });
- });
- class TestEvent extends VendureEvent {
- constructor(public payload: string) {
- super();
- }
- }
- class ChildTestEvent extends TestEvent {
- constructor(
- public childPayload: string,
- payload: string,
- ) {
- super(payload);
- }
- }
- class OtherTestEvent extends VendureEvent {
- constructor(public payload: string) {
- super();
- }
- }
|